Little updates on german

Little updates on german

--- coffeescripts/cards-de.coffee
+++ coffeescripts/cards-de.coffee
@@ -234,7 +234,7 @@
             text: """Wenn du die Aktion Fassrolle ausführst, kannst du 1 Stressmarker erhalten, um die (%BANKLEFT% 1) oder (%BANKRIGHT% 1) Manöverschablone anstatt der (%STRAIGHT% 1) Manöverschablone zu benutzen."""
         "Royal Guard Pilot":
             ship: "TIE-Abfangjäger"
-            name: "Royal Guard Pilot"
+            name: "Pilot der Roten Garde"
         "Tetran Cowall":
             ship: "TIE-Abfangjäger"
             text: """Immer wenn du ein %UTURN% Manöver aufdeckst, kannst du das Manöver mit einer Geschwindigkeit von "1," "3," oder "5" ausführen."""
@@ -287,7 +287,7 @@
             name: "Pilot der Schatten-Staffel"
         '"Echo"':
             name: '"Echo"'
-            text: """Wenn du dich enttarnst, muss du statt der (%STRAIGHT% 2)-Manöverschablone die (%BANKRIGHT% 2)- oder (%BANKLEFT% 2)-Schablone verwenden."""
+            text: """Wenn du dich enttarnst, musst du statt der (%STRAIGHT% 2)-Manöverschablone die (%BANKRIGHT% 2)- oder (%BANKLEFT% 2)-Schablone verwenden."""
         '"Whisper"':
             name: '"Geflüster"'
             text: """Nachdem du mit einem Angriff getroffen hast, darfst du deinem Schiff 1 Fokusmarker geben."""


Force Awaken Core Set translation

Force Awaken Core Set translation

-Translated the expansion name
-Added and translated Tech upgrade
-Added the translation for the T-70 and TIE/fo ships*
-Added the "ship: ###" tag to both, T-70 and TIE/fo pilots (including the single expansion packs) to avoid problems with the ship name change*
-Translated all the pilots, adding the non-unique missing ones
-Added the unspoiled pilots from the single expansions. They have no purpose right now, but this way there will be no chance for me to miss them once they are spoiled/published, while being more faithful to the original (english) version of the Builder.
-Translated the upgrade cards

*If I remember correctly, adding the translated ship name to each pilot should avoid the problems this could case (pilots disappearing from the builder and so). The first time I translated ship names I think there was some conflict with a couple Test files, but that didn't happen the last few times... so I hope will still be the case :P

--- coffeescripts/cards-es.coffee
+++ coffeescripts/cards-es.coffee
@@ -32,6 +32,7 @@
         "Team": "Equipo"
         "Illicit": "Ilícita"
         "Salvaged Astromech": "Droide Astromech Remendado"
+        "Tech": "Tecnología"
     sources: # needed?
         "Core": "Caja Básica"
         "A-Wing Expansion Pack": "Pack de Expansión Ala-A"
@@ -65,7 +66,7 @@
         "TIE Punisher Expansion Pack": "Pack de Expansión Castigador TIE"
         "Kihraxz Fighter Expansion Pack": "Pack de Expansión Caza Kihraxz"
         "Hound's Tooth  Expansion Pack": "Pack de Expansión Diente de Perro"
-        "The Force Awakens Core Set": "The Force Awakens Core Set"
+        "The Force Awakens Core Set": "Caja Básica El Despertar de la Fuerza"
     ui:
         shipSelectorPlaceholder: "Selecciona una nave"
         pilotSelectorPlaceholder: "Selecciona un piloto"
@@ -161,6 +162,7 @@
     exportObj.renameShip 'TIE Defender', 'Defensor TIE'
     exportObj.renameShip 'TIE Punisher', 'Castigador TIE'
     exportObj.renameShip 'VT-49 Decimator', 'VT-49 Diezmador'
+    exportObj.renameShip 'TIE/fo Fighter', 'Caza TIE/fo'
     exportObj.renameShip 'A-Wing', 'Ala-A'
     exportObj.renameShip 'B-Wing', 'Ala-B'
     exportObj.renameShip 'E-Wing', 'Ala-E'
@@ -171,6 +173,7 @@
     exportObj.renameShip 'CR90 Corvette (Aft)', 'Corbeta CR90 (Popa)'
     exportObj.renameShip 'CR90 Corvette (Fore)', 'Corbeta CR90 (Proa)'
     exportObj.renameShip 'GR-75 Medium Transport', 'Transporte mediano GR-75'
+    exportObj.renameShip 'T-70 X-Wing', 'T-70 Ala-X'
     exportObj.renameShip 'M3-A Interceptor', 'Interceptor M3-A'
     exportObj.renameShip 'StarViper', 'Víbora Estelar'
     exportObj.renameShip 'Aggressor', 'Agresor'
@@ -634,20 +637,52 @@
             text: """Cuando realices un ataque con éxito, antes de inflingir el daño puedes anular 1 de tus resultados %CRIT% para añadir 2 resultados %HIT%."""
         # T-70
         "Poe Dameron":
-            text: """When attacking or defending, if you have a focus token, you may change 1 of your %FOCUS% results to a %HIT% or %EVADE% result."""
+            ship: "T-70 Ala-X"
+            text: """Cuando ataques o te defiendas, si tienes una ficha de Concentración, puedes cambiar 1 de tus resultados %FOCUS% por un resultado %HIT% o %EVADE%."""
         '"Blue Ace"':
-            text: """When performing a boost action, you may use the (%TURNLEFT% 1) or (%TURNRIGHT% 1) template."""
+            name: '"As Azul"'
+            ship: "T-70 Ala-X"
+            text: """Cuando realices una acción de impulso, puedes utilizar la plantilla de maniobra (%TURNLEFT% 1) o (%TURNRIGHT% 1)."""
+        "Red Squadron Veteran":
+            name: "Veterano del Esc. Rojo"
+            ship: "T-70 Ala-X"
+        "Blue Squadron Novice":
+            name: "Novato del Esc. Azul"
+            ship: "T-70 Ala-X"
+        '"Red Ace"':
+            ship: "T-70 Ala-X"
+            text: '''The first time you remove a shield token from your ship each round, assign 1 evade token to your ship.'''
+        "Unspoiled PS7 T-70 X-Wing Pilot"
+            ship: "T-70 Ala-X"
         # TIE/fo
         '"Omega Ace"':
-            text: """When attacking, you may spend a focus token and a target lock you have on the defender to change all of your results to %CRIT% results."""
+            name: '"As Omega"'
+            ship: "Caza TIE/fo"
+            text: """Cuando ataques a un defensor que has fijado como blanco, puedes gastar las fichas de Blanco Fijado y una ficha de Concentración para cambiar todos tus resultados de dados por resultados %CRIT%."""
         '"Epsilon Leader"':
-            text: """At the start of the Combat phase, remove 1 stress token from each friendly ship at Range 1."""
+            name: '"Jefe Epsilon"'
+            ship: "Caza TIE/fo"
+            text: """Al comienzo de la fase de Combate, retira 1 ficha de Tensión de cada nave aliada que tengas a alcance 1."""
         '"Zeta Ace"':
-            text: """When performing a barrel roll you may use the (%STRAIGHT% 2) template instead of the (%STRAIGHT% 1) template."""
-        '"Red Ace"':
-            text: '''The first time you remove a shield token from your ship each round, assign 1 evade token to your ship.'''
+            name: '"As Zeta"'
+            ship: "Caza TIE/fo"
+            text: """Cuando realices una acción de tonel volado, puedes utilizar la plantilla de maniobra (%STRAIGHT% 2) en vez de la plantilla (%STRAIGHT% 1)."""
+        "Omega Squadron Pilot":
+            name: "Piloto del Esc. Omega"
+            ship: "Caza TIE/fo"
+        "Zeta Squadron Pilot":
+            name: "Piloto del Esc. Zeta"
+            ship: "Caza TIE/fo"
+        "Epsilon Squadron Pilot":
+            name: "Piloto del Esc. Epsilon"
+            ship: "Caza TIE/fo"
         '"Omega Leader"':
+            ship: "Caza TIE/fo"
             text: '''Enemy ships that you have locked cannot modify any dice when attacking you or defending against your attacks.'''
+        "Unspoiled PS4 TIE/fo Pilot"
+            ship: "Caza TIE/fo"
+        "Unspoiled PS7 TIE/fo Pilot"
+            ship: "Caza TIE/fo"

     upgrade_translations =
         "Ion Cannon Turret":
@@ -1071,13 +1106,15 @@
             name: "Munición Adicional"
             text: """Cuando te equipas con esta carta, coloca 1 ficha de Munición de artillería sobre cada carta de Mejora %TORPEDO%, %MISSILE% y %BOMB% que tengas equipada. Cuando se te indique que descartes una carta de Mejora, en vez de eso puedes descartar 1 ficha de Munición de artillería que haya encima de esa carta."""
         "Weapons Guidance":
-            text: """When attacking, you may spend a focus token to change 1 of your blank results to a %HIT% result."""
+            name: "Sistema de Guiado de Armas"
+            text: """Cuando ataques, puedes gastar una ficha de Concentración para cambiar 1 de tus resultados de cara vacia por un resultado %HIT%."""
         "BB-8":
-            text: """When you reveal a green maneuver, you may perform a free barrel roll action."""
+            text: """Cuando reveles una maniobra verde, puedes realizar una acción gratuita de tonel volado."""
         "R5-X3":
-            text: """Before you reveal your maneuver, you may discard this card to ignore obstacles until the end of the round."""
+            text: """Antes de revelar tu maniobra, puedes descartar esta carta para ignorar todos los obstáculos hasta el final de la ronda."""
         "Wired":
-            text: """When attacking or defending, if you are stressed, you may reroll 1 or more of your %FOCUS% results."""
+            name: "Enardecido"
+            text: """Cuando ataques o te defiendas, si estás bajo tensión, puedes volver a tirar 1 o más de tus resultados %FOCUS%."""
         'Cool Hand':
             text: '''When you receive a stress token, you may discard this card to assign 1 focus or evade token to your ship.'''
         'Juke':


Removing half baked test.

Removing half baked test.

--- tests/test_engine.coffee
+++ tests/test_engine.coffee
@@ -26,26 +26,3 @@

     e.remove(r2)
     equals(e.elements.length, 1, "We can remove elements")
-
-test 'step', () ->
-
-    class TestElement extends wolf.Element
-        elapse : (elapsed, iteration) ->
-            console.log('aaa')
-            ok(elapsed, "passed ms elapsed")
-            ok(iteration, "passed iteration")
-            @counter = if @counter? then @counter + 1 else 1
-
-        render : () ->
-            this
-
-    # Start the engine, but don't automatically step, so we can control
-    # those.
-    engine = new wolf.Engine("test-canvas")
-    engine.start()
-
-    # Listen for elapsed events.
-    element = new TestElement()
-    engine.add(element)
-
-    equals(element.counter, 2, "the elapsed handler was executed twice")


set document.title based on doc.name

set document.title based on doc.name

--- neuron-catalog.coffee
+++ neuron-catalog.coffee
@@ -1,6 +1,9 @@
+DEFAULT_TITLE = 'neuron catalog'
 Router.configure
   layoutTemplate: "MyLayout"
   notFoundTemplate: "PageNotFound"
+  onAfterAction: ->
+    document.title = DEFAULT_TITLE

 Router.setTemplateNameConverter (str) ->
   str
@@ -43,6 +46,12 @@
     always_include_name_in_path_action(this, DriverLines, "driver_line_show")
   data: ->
     DriverLines.findOne _id: @params._id
+  onAfterAction: ->
+    doc = DriverLines.findOne _id: @params._id
+    if doc?
+      document.title = doc.name + ' - neuron catalog'
+    else
+      document.title = DEFAULT_TITLE

 Router.route "/neuron_types"

@@ -52,6 +61,12 @@
     always_include_name_in_path_action(this, NeuronTypes, "neuron_type_show")
   data: ->
     NeuronTypes.findOne _id: @params._id
+  onAfterAction: ->
+    doc = NeuronTypes.findOne _id: @params._id
+    if doc?
+      document.title = doc.name + ' - neuron catalog'
+    else
+      document.title = DEFAULT_TITLE

 Router.route "/neuropils"

@@ -61,6 +76,12 @@
     always_include_name_in_path_action(this, Neuropils, "neuropil_show")
   data: ->
     Neuropils.findOne _id: @params._id
+  onAfterAction: ->
+    doc = Neuropils.findOne _id: @params._id
+    if doc?
+      document.title = doc.name + ' - neuron catalog'
+    else
+      document.title = DEFAULT_TITLE

 Router.route "/binary_data"

@@ -70,6 +91,12 @@
     always_include_name_in_path_action(this, BinaryData, "binary_data_show")
   data: ->
     BinaryData.findOne _id: @params._id
+  onAfterAction: ->
+    doc = BinaryData.findOne _id: @params._id
+    if doc?
+      document.title = doc.name + ' - neuron catalog'
+    else
+      document.title = DEFAULT_TITLE

 Router.route "/RecentChanges"


Send error to outPorts

Send error to outPorts

--- components/GetGIFFrame.coffee
+++ components/GetGIFFrame.coffee
@@ -29,10 +29,10 @@
   noflo.helpers.WirePattern c,
     in: 'in'
     params: 'frame'
-    out: 'out'
+    out: ['out', 'error']
     forwardGroups: true
     async: true
-  , (payload, groups, out, callback) ->
+  , (payload, groups, outPorts, callback) ->
     chunk = readChunk payload, 0, 262
     frame = if c.params.frame then c.params.frame else 0
     chunk.then (buffer) ->
@@ -40,7 +40,9 @@
       unless type
         err = new Error 'Unsupported MIME type'
         err.payload = payload
-        return callback err
+        outPorts.error.send err
+        do callback
+        return
       if type.ext is 'gif'
         tmpFile = new temporary.File
         gm payload
@@ -49,12 +51,14 @@
           if err
             tmpFile.unlink()
             err.payload = payload
-            return callback err
-          out.send tmpFile.path
+            outPorts.error.send err
+            do callback
+            return
+          outPorts.out.send tmpFile.path
           do callback
           return
       else
         # Not a GIF, just send the tempfile path along
-        out.send payload
+        outPorts.out.send payload
         do callback
         return


Make all detection values configurables

Make all detection values configurables

--- framer/GestureInputRecognizer.coffee
+++ framer/GestureInputRecognizer.coffee
@@ -1,4 +1,10 @@
 Utils = require "./Utils"
+
+GestureInputLongPressTime = 0.25
+GestureInputDoubleTapTime = 0.25
+GestureInputSwipeThreshold = 30
+GestureInputEdgeSwipeDistance = 30
+GestureInputVelocityTime = 0.1

 {DOMEventManager} = require "./DOMEventManager"

@@ -25,7 +31,7 @@
 			startEvent: @_getGestureEvent(event)
 			startMultiEvent: null
 			startTime: Date.now()
-			pressTimer: window.setTimeout(@longpressstart, 250)
+			pressTimer: window.setTimeout(@longpressstart, GestureInputLongPressTime * 1000)
 			started: {}
 			events: []

@@ -33,7 +39,7 @@

 		@tapstart(event)

-		if Date.now() - @doubleTapTime < 250
+		if Date.now() - @doubleTapTime < (GestureInputDoubleTapTime * 1000)
 			@doubletap(event)
 		else
 			@doubleTapTime = Date.now()
@@ -153,13 +159,13 @@
 		maxX = Utils.frameGetMaxX(Screen.canvasFrame)
 		maxY = Utils.frameGetMaxY(Screen.canvasFrame)

-		if swipeEdge is "top" and 0 < event.start.y - Screen.canvasFrame.y < 30
+		if swipeEdge is "top" and 0 < event.start.y - Screen.canvasFrame.y < GestureInputEdgeSwipeDistance
 			@edgeswipedirectionstart(event)
-		if swipeEdge is "right" and maxX - 30 < event.start.x < maxX
+		if swipeEdge is "right" and maxX - GestureInputEdgeSwipeDistance < event.start.x < maxX
 			@edgeswipedirectionstart(event)
-		if swipeEdge is "bottom" and maxY - 30 < event.start.y < maxY
+		if swipeEdge is "bottom" and maxY - GestureInputEdgeSwipeDistance < event.start.y < maxY
 			@edgeswipedirectionstart(event)
-		if swipeEdge is "left" and 0 < event.start.x - Screen.canvasFrame.x < 30
+		if swipeEdge is "left" and 0 < event.start.x - Screen.canvasFrame.x < GestureInputEdgeSwipeDistance
 			@edgeswipedirectionstart(event)

 	swipedirection: (event) =>
@@ -224,7 +230,9 @@
 		# Detect swipe events

 		# If we did not start but moved more then the swipe threshold, start
-		if not @session.started.swipe and (Math.abs(event.offset.x) > 30 or Math.abs(event.offset.y) > 30)
+		if not @session.started.swipe and (
+			Math.abs(event.offset.x) > GestureInputSwipeThreshold or
+			Math.abs(event.offset.y) > GestureInputSwipeThreshold)
 				@swipestart(event)
 		# If we did start send swipe events
 		else if @session.started.swipe
@@ -279,7 +287,7 @@
 				event.delta = Utils.pointSubtract(event.touchCenter, @session.lastEvent.touchCenter)

 		if @session?.events
-			events = _.filter @session.events, (e) -> e.time > (event.time - 100)
+			events = _.filter @session.events, (e) -> e.time > (event.time - GestureInputVelocityTime)
 			event.velocity = @_getVelocity(events)

 		if event.touches.length > 0


Improved documentation. Refactored internally used rondell properties.

--- src/jquery.rondell.coffee
+++ src/jquery.rondell.coffee
@@ -10,76 +10,76 @@
 ###

 (($) ->
-  # Global rondell stuff
+  ### Global rondell plugin properties ###
   $.rondell =
     version: '0.8.3'
     name: 'rondell'
     defaults:
-      showContainer: true
+      showContainer: true       # When the plugin has finished initializing $.show() will be called on the items container
       resizeableClass: 'resizeable'
       smallClass: 'itemSmall'
       hiddenClass: 'itemHidden'
-      itemCount: 0 # Number of rondell items in a rondell
-      currentLayer: 1 # Active layer number
-      container: null
-      radius: # Radius if the rondell uses a circle function
+      itemCount: 0              # Number of rondell items in a rondell instance
+      currentLayer: 1           # Active layer number in a rondell instance
+      container: null           # Container object wrapping the rondell items
+      radius:                   # Radius for the default circle function
         x: 300
         y: 50
-      center: # Center where the focused element is displayed
+      center:                   # Center where the focused element is displayed
         left: 400
         top: 200
-      size: # Defaults to center * 2
+      size:                     # Defaults to center * 2 on init
         width: null
         height: null
-      visibleItems: 'auto' # How many items should be visible in each direction
-      scaling: 2 # Size of focused element
-      opacityMin: 0.05 # Min opacity before elements are set to display: none
+      visibleItems: 'auto'      # How many items should be visible in each direction
+      scaling: 2                # Size of focused element
+      opacityMin: 0.05          # Min opacity before elements are set to display: none
       fadeTime: 300
-      zIndex: 1000 # All elements of the rondell will use this z-index and add their depth to it
-      itemProperties: # Default properties for each item
-        delay: 100
-        cssClass: 'rondellItem'
+      zIndex: 1000              # All elements of the rondell will use this z-index and add their depth to it
+      itemProperties:           # Default properties for each item
+        delay: 100              # Time offset between the animation of each item
+        cssClass: 'rondellItem' # Identifier for each item
         size:
           width: 150
           height: 150
         sizeFocused:
           width: 0
           height: 0
-      repeating: true # Rondell will go forever
-      alwaysShowCaption: false
-      autoRotation: # If the cursor leaves the rondell continue spinning
+      repeating: true           # Will show first item after last item and so on
+      alwaysShowCaption: false  # Don't hide caption on mouseleave
+      autoRotation:             # If the cursor leaves the rondell continue spinning
         enabled: false
-        paused: false
-        timer: -1
-        direction: 0
-        once: false
+        paused: false           # Can be used to pause the auto rotation with a play/pause button for example
+        _timer: -1
+        direction: 0            # 0 or 1 means left and right
+        once: false             # Will animate until the rondell will be hovered at least once
         delay: 5000
-      controls: # Buttons to control the rondell
+      controls:                 # Buttons to control the rondell
         enabled: true
-        fadeTime: 400
-        margin:
-          x: 20
-          y: 20
+        fadeTime: 400           # Show/hide animation speed
+        margin:
+          x: 20                 # Distance from left and right edge of the container
+          y: 20                 # Distance from top and bottom edge of the container
       strings: # String for the controls
         prev: 'prev'
         next: 'next'
       touch:
         enabled: true
-        preventDefaults: true
-        threshold: 100
-        start: undefined
-        end: undefined
-      funcEase: 'easeInOutQuad' # Easing function name for the movement of items
-      theme: 'default' # CSS theme class which gets added to the rondell container
-      preset: '' # Configuration preset
-      effect: null # Special effect function for the focused item
+        preventDefaults: true   # Will call event.preventDefault() on touch events
+        threshold: 100          # Distance in pixels the "finger" has to swipe to create the touch event
+        _start: undefined
+        _end: undefined
+      funcEase: 'easeInOutQuad' # jQuery easing function name for the movement of items
+      theme: 'default'          # CSS theme class which gets added to the rondell container
+      preset: ''                # Configuration preset
+      effect: null              # Special effect function for the focused item, not used currently

-  # Add default easing function for rondell to jQuery if missing
+  ### Add default easing function for rondell to jQuery if missing ###
   unless $.easing.easeInOutQuad
     $.easing.easeInOutQuad = (x, t, b, c, d) ->
       if ((t/=d/2) < 1) then c/2*t*t + b else -c/2 * ((--t)*(t-2) - 1) + b

-  # Rondell class
+  # Rondell class holds all rondell items and functions
   class Rondell
     @rondellCount: 0
     @activeRondell: null # Stores the last activated rondell for keyboard interaction
@@ -290,17 +290,17 @@

       switch e.type
         when 'touchstart'
-          @touch.start =
+          @touch._start =
             x: touch.pageX
             y: touch.pageY
         when 'touchmove'
           e.preventDefault() if @touch.preventDefaults
-          @touch.end =
+          @touch._end =
             x: touch.pageX
             y: touch.pageY
         when 'touchend'
-          if @touch.start and @touch.end
-            changeX = @touch.end.x - @touch.start.x
+          if @touch._start and @touch._end
+            changeX = @touch._end.x - @touch._start.x
             if Math.abs(changeX) > @touch.threshold
               if changeX > 0
                 @shiftLeft()
@@ -308,7 +308,7 @@
                 @shiftRight()

             # Reset end position
-            @touch.start = @touch.end = undefined
+            @touch._start = @touch._end = undefined

       true

@@ -417,7 +417,7 @@
               , fadeTime
             )
       else if item.hidden
-        # Update position even if out of view to fix animation when reappearing
+        ### Update position even if out of view to fix animation when reappearing ###
         item.object.css(
           left: newX
           top: newY
@@ -461,10 +461,10 @@

     _autoShift: =>
       autoRotation = @autoRotation
-      if @isActive() and autoRotation.enabled and autoRotation.timer < 0
+      if @isActive() and autoRotation.enabled and autoRotation._timer < 0
         # store timer id
-        autoRotation.timer = window.setTimeout( =>
-            @autoRotation.timer = -1
+        autoRotation._timer = window.setTimeout( =>
+            @autoRotation._timer = -1
             if @isActive() and not autoRotation.paused
               if autoRotation.direction then @shiftRight() else @shiftLeft()
           , autoRotation.delay
@@ -476,9 +476,9 @@
     keyDown: (e) =>
       if @isActive() and Rondell.activeRondell is @.id
         # Clear current rotation timer on user interaction
-        if @autoRotation.timer >= 0
-          window.clearTimeout(@autoRotation.timer)
-          @autoRotation.timer = -1
+        if @autoRotation._timer >= 0
+          window.clearTimeout(@autoRotation._timer)
+          @autoRotation._timer = -1

         switch e.which
           # arrow left


:art: Remove nested if

:art: Remove nested if

--- src/keymap-manager.coffee
+++ src/keymap-manager.coffee
@@ -419,15 +419,14 @@
     @queuedKeystrokes.push(keystroke)
     @queuedKeyboardEvents.push(event)

-    if queuedKeystrokes?
-      if hasPartialMatches
-        enableTimeout = (
-          @pendingStateTimeoutHandle? or
-          exactMatch? or
-          characterForKeyboardEvent(@queuedKeyboardEvents[0])?
-        )
-        enableTimeout = false if isReplay
-        @enterPendingState(partialMatches, enableTimeout)
+    if queuedKeystrokes? and hasPartialMatches
+      enableTimeout = (
+        @pendingStateTimeoutHandle? or
+        exactMatch? or
+        characterForKeyboardEvent(@queuedKeyboardEvents[0])?
+      )
+      enableTimeout = false if isReplay
+      @enterPendingState(partialMatches, enableTimeout)
     else if not exactMatch? and not hasPartialMatches and @pendingPartialMatches?
       @terminatePendingState()
     else


Fix test.

Fix test.

--- test/src/cstest.coffee
+++ test/src/cstest.coffee
@@ -189,8 +189,7 @@
       precedence="0"
       color="purple"
       socketLevel="0"
-      classes="Assign mostly-block"
-    ><socket
+      classes="Assign mostly-block"><socket
       precedence="0"
       handwritten="false"
       classes="Value lvalue"
@@ -198,7 +197,7 @@
     > = (<socket
       precedence="0"
       handwritten="false"
-      classes="Param forbid-all"
+      classes="forbid-all __function_param__"
     >y</socket
     >) -> <socket
       precedence="0"
@@ -228,7 +227,7 @@
       precedence="0"
       handwritten="false"
       classes="Value"
-    >\'clickme\'</socket
+    >'clickme'</socket
     >, -><indent
       prefix="  "
       classes=""
@@ -241,7 +240,8 @@
       precedence="-1"
       handwritten="false"
       classes="Value"
-    >\'ouch\'</socket></block></indent></block></document>'''
+    >'ouch'</socket></block></indent></block></document>
+  '''
   strictEqual(
       helper.xmlPrettyPrint(customSerialization),
       helper.xmlPrettyPrint(expectedSerialization),


remove reference to chai

remove reference to chai

--- spec/Mock.coffee
+++ spec/Mock.coffee
@@ -1,4 +1,4 @@
-expect = require("chai").expect
+expect = require("expect.js")

 class Mock
   constructor: (methodNames...) ->
@@ -36,7 +36,7 @@
       throw "not called: #{name}"
     actualCall = calls[0]
     calls.splice(0,1)
-    expect(actualCall).to.deep.equal(args)
+    expect(actualCall).to.eql(args)
   method.doReturn = (returnValue) ->
     {
       when: (args...) ->


add a queue to Dispatcher to cope with events arising during dispatching

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -312,9 +312,8 @@
         if count > 0
           @push event
         else
-          if count == 0
-            @push event
-            @push end()
+          @push event
+          @push end()
           Bacon.noMore

   map: (f, args...) ->
@@ -709,31 +708,46 @@
   constructor: (subscribe, handleEvent) ->
     subscribe ?= -> nop
     sinks = []
+    queue = null
+    pushing = false
     ended = false
     @hasSubscribers = -> sinks.length > 0
     unsubscribeFromSource = nop
     removeSink = (sink) ->
       sinks = _.without(sink, sinks)
-    waiters = []
+    waiters = null
     done = (event) ->
       if waiters?
         ws = waiters
-        waiters = undefined
+        waiters = null
         w() for w in ws
       event.onDone = Event.prototype.onDone
-    addWaiter = (listener) -> waiters.push(listener)
+    addWaiter = (listener) -> waiters = (waiters or []).concat([listener])
     @push = (event) =>
-      waiters = []
-      event.onDone = addWaiter
-      tmpSinks = sinks
-      for sink in tmpSinks
-        reply = sink event
-        removeSink sink if reply == Bacon.noMore or event.isEnd()
-      done(event)
-      if @hasSubscribers()
-        Bacon.more
-      else
-        Bacon.noMore
+      if not pushing
+        doPush event
+      else
+        queue = (queue or []).concat([event])
+        Bacon.more
+    doPush = (event) =>
+      try
+        pushing = true
+        event.onDone = addWaiter
+        tmpSinks = sinks
+        for sink in tmpSinks
+          reply = sink event
+          removeSink sink if reply == Bacon.noMore or event.isEnd()
+        done(event)
+        if queue?.length
+          first = _.head(queue)
+          queue = _.tail(queue)
+          doPush first
+        else if @hasSubscribers()
+          Bacon.more
+        else
+          Bacon.noMore
+      finally
+        pushing = false
     handleEvent ?= (event) -> @push event
     @handleEvent = (event) =>
       if event.isEnd()


return undefined to prevent unnecessary collection of values into array

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -1548,6 +1548,7 @@
   each: (xs, f) ->
     for key, value of xs
       f(key, value)
+    undefined
   toArray: (xs) -> if isArray(xs) then xs else [xs]
   contains: (xs, x) -> _.indexOf(xs, x) != -1
   id: (x) -> x


Simplify skipUntil

Simplify skipUntil

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -640,10 +640,8 @@
       .map(({value}) -> value)

   skipUntil: (starter) ->
-    started = starter.map(true).toProperty(false).take(2)
-    started.sampledBy(this, (started, val) -> { val, started })
-           .filter(({started}) -> started)
-           .map(({val}) -> val)
+    started = starter.map(true).toProperty(false)
+    this.filter(started)

   skipWhile: (f, args...) ->
     convertArgsToFunction this, f, args, (f) ->


fix sequentially

fix sequentially

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -14,13 +14,14 @@

 Bacon.sequentially = (delay, values) ->
   new EventStream ((sink) ->
+      schedule = (xs) -> setTimeout (-> push xs) delay
       push = (xs) ->
-        sink (head xs)
         if empty xs
           sink Bacon.end
         else
-          Bacon.sequentially(delay, tail values)
-      setTimeout (-> push values) delay
+          sink (head xs)
+          schedule (tail xs)
+      schedule values
   )

 class EventStream


:arrow_up: add way to specifying navigation of link component by unique route

--- src/renderer/components/link-component.coffee
+++ src/renderer/components/link-component.coffee
@@ -17,19 +17,45 @@
 Example:
 <Link href='/index'>Index</Link>

+-- By specifying unique route
+If uniqRoute is specified, search path from RouteStore and give set to component's
+href attribute.
+Warning: Even if uniqRoute is not unique, path(for exapmle '.*' including regexp
+in the path) is permanently set to href.
+Warning: routes are not always up-to-date because @state.routes is not link to
+RouteStore state due to a performance probrem.
+
+Example:
+<Link uniqRoute='index'>Index</Link>
+
 ###

 class LinkComponent extends React.Component
   constructor: (props) ->
     super props

+  componentWillMount: ->
+    @store = @context.ctx.routeStore
+    state = @store.get()
+    @setState
+      routes: state.routes
+    @href = '#'
+
   navigate: (e) ->
     e.preventDefault()
     e.stopPropagation()
-    @context.ctx.routeAction.navigate(@props.href)
+    @context.ctx.routeAction.navigate(@href)

   render: ->
-
+    @href = '#'
+    if @props.href
+      @href = @props.href
+    else if @props.uniqRoute?
+      for fragment, route of @state.routes
+         if route.match new RegExp("^#{@props.uniqRoute}$")
+           @href = "/#{fragment}"
+           break
+
       {@props.children}


Update analytics.js api

Update analytics.js api

--- src/scripts/helpers/handlers/analytics.coffee
+++ src/scripts/helpers/handlers/analytics.coffee
@@ -13,7 +13,7 @@
       window.ga.l = 1 * new Date()

       # Initialize analytics.js account
-      window.ga('create', settings.analyticsID)
+      window.ga('create', settings.analyticsID, 'auto')

       # ## Setup ga.js
       window._gaq ?= []


fix path for demo website

fix path for demo website

--- source/assets/javascripts/jquery-law.js.coffee
+++ source/assets/javascripts/jquery-law.js.coffee
@@ -5,7 +5,7 @@
     alert.loadAlert()

   constructor: (options={}) ->
-    @cookieUrl      = options.cookieUrl      ? '/cookies'
+    @cookieUrl      = options.cookieUrl      ? 'cookies'
     @messageTitle   = options.messageTitle   ? 'Cookies'
     @messageContent = options.messageContent ? 'En poursuivant votre navigation sur notre site, vous en acceptez l‘utilisation pour vous proposer un service personnalisé, des publicités ciblées adaptées à vos centres d’intérêts et réaliser des statistiques de visites.'
     @cookieName     = options.cookieName     ? 'cookies_law'


Blacklisting certain globals from REPL rather than whitelisting (fixes #1654)

--- repl.coffee
+++ repl.coffee
@@ -33,12 +33,15 @@

 # The REPL context; must be visible outside `run` to allow for tab completion
 sandbox = Script.createContext()
+excludedGlobals = [
+  'global', 'GLOBAL', 'root'
+]
 nonContextGlobals = [
   'Buffer', 'console', 'process'
   'setInterval', 'clearInterval'
   'setTimeout', 'clearTimeout'
 ]
-sandbox[g] = global[g] for g in nonContextGlobals
+sandbox[g] = global[g] for g of global when g not in excludedGlobals
 sandbox.global = sandbox.root = sandbox.GLOBAL = sandbox

 # The main REPL function. **run** is called every time a line of code is entered.


correction

correction

--- collections/posts.coffee
+++ collections/posts.coffee
@@ -12,11 +12,7 @@

 	category:
 		type: String
-		optional: true
 	    allowedValues: Utils.categoryList
-	    autoform:
-	    	type:"select"
-	    	options: Utils.categoryList

 	location:
     type: String


Fix listeners removal

Fix listeners removal

--- admin/static/coffee/namespaces/namespace.coffee
+++ admin/static/coffee/namespaces/namespace.coffee
@@ -405,9 +405,9 @@
             @history_opsec.push @model.get_stats().keys_read + @model.get_stats().keys_set

         destroy: =>
-            machines.off 'change', @render
-            @model.off 'change:key_distr', @render
-            @model.off 'change:shards', @render
+            machines.off 'change', @render_data_in_memory
+            @model.off 'change:key_distr_sorted', @render_data_repartition
+            @model.off 'change:shards', @render_data_repartition

     class @Other extends Backbone.View
         className: 'namespace-other'


Checkout 001-schema.sql file after koding/postgres image is built

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

--- config/generateRunFile.coffee
+++ config/generateRunFile.coffee
@@ -407,6 +407,7 @@
       sed -i -e "s/PASSWORD 'kontrolapplication'/PASSWORD '$KONFIG_KONTROL_POSTGRES_PASSWORD'/" kontrol/001-schema.sql
       sed -i -e "s/GRANT kontrol TO kontrolapplication/GRANT kontrol TO $KONFIG_KONTROL_POSTGRES_USERNAME/" kontrol/001-schema.sql
       docker build -t koding/postgres .
+      git checkout kontrol/001-schema.sql
       runMongoDocker
       docker run -d -p 5672:5672 -p 15672:15672                           --name=rabbitmq rabbitmq:3-management
       docker run -d -p 6379:6379                                          --name=redis    redis


accountpopup: remove Upgrade plan link from account menu

--- client/app/lib/avatararea/accountpopup.coffee
+++ client/app/lib/avatararea/accountpopup.coffee
@@ -1,7 +1,8 @@
-kd                        = require 'kd'
-AvatarPopup               = require './avatarpopup'
-CustomLinkView            = require '../customlinkview'
-HelpSupportModal          = require '../commonviews/helpsupportmodal'
+kd                = require 'kd'
+AvatarPopup       = require './avatarpopup'
+CustomLinkView    = require '../customlinkview'
+HelpSupportModal  = require '../commonviews/helpsupportmodal'
+isSoloProductLite = require 'app/util/issoloproductlite'

 module.exports = class AccountPopup extends AvatarPopup

@@ -13,11 +14,12 @@

     { groupsController } = kd.singletons

-    @avatarPopupContent.addSubView @paymentActionLabel = new CustomLinkView
-      title      : 'Upgrade plan'
-      href       : '/Pricing'
-      cssClass   : 'bottom-separator'
-      click      : @bound 'goToPricing'
+    unless issoloproductlite()
+      @avatarPopupContent.addSubView @paymentActionLabel = new CustomLinkView
+        title      : 'Upgrade plan'
+        href       : '/Pricing'
+        cssClass   : 'bottom-separator'
+        click      : @bound 'goToPricing'

     @avatarPopupContent.addSubView new CustomLinkView
       title      : 'Koding University'


HBW-73 Make sync request for checking bpm user (#162)

--- hbw/app/assets/javascripts/hbw/widget.js.jsx.coffee
+++ hbw/app/assets/javascripts/hbw/widget.js.jsx.coffee
@@ -93,7 +93,8 @@
       checkBpmUser: =>
         @env.connection.request(
           url: @env.connection.serverURL + '/users/check'
-          method: 'GET').done((data) =>
+          method: 'GET'
+          async: false).done((data) =>
             unless data.user_exist
               @env.dispatcher.trigger('hbw:bpm-user-not-found')
               @env.userExist = false


Make build_detail prettier

Make build_detail prettier

--- build_detail.coffee
+++ build_detail.coffee
@@ -278,115 +278,119 @@
       log: "Unsupported platform #{platform}."

-module.exports =
-  buildLocalAsync: ->
-    download_dir = path.join __dirname, build_dir_name, download_dir_name
-    theme_root = path.join __dirname, 'assets', 'themes'
-    flash_dir = path.join __dirname, 'PepperFlash'
-    npm_path = 'npm'
-    bower_path = path.join(__dirname, 'node_modules', '.bin', 'bower')
-
-    download_theme = downloadThemesAsync theme_root, download_dir
-    install_flash = installFlashAsync os.platform(), os.arch(), download_dir,
-      flash_dir
-    install_npm_bower = npmInstallAsync npm_path, __dirname
-    .then -> (async -> yield bowerInstallAsync bower_path, __dirname)()
-
-    Promise.join download_theme, install_flash, install_npm_bower
-
-
-  buildAsync: async (poi_version, electron_version, platform_arch_list) ->
-    build_root = path.join __dirname, build_dir_name
-
-    download_dir = path.join build_root, download_dir_name
-    building_root = path.join build_root, "building_#{poi_version}"
-    release_dir = path.join build_root, release_dir_name
-
-    stage1_app = path.join building_root, 'stage1'
-    stage2_app = path.join building_root, 'app'
-
-    theme_root = path.join stage1_app, 'assets', 'themes'
-
-    npm_path = 'npm'
-    bower_path = path.join(__dirname, 'node_modules', '.bin', 'bower')
-
-    try
-      yield Promise.join \
-        (fs.removeAsync stage1_app),
-        (fs.removeAsync stage2_app)
-    catch e
-    fs.ensureDirSync stage1_app
-    fs.ensureDirSync stage2_app
-
-    # Check npm version
-    npm_version = (yield execAsync "'#{npm_path}' --version").trim()
-    log "You are using npm v#{npm_version}"
-    if semver.major(npm_version) == 2
-      log "*** USING npm 2 TO BUILD poi IS PROHIBITED ***"
-      log "Aborted."
-      return
-
-    # Prepare stage1
-    download_themes = downloadThemesAsync theme_root, download_dir
-    archive_app = execAsync "git archive HEAD | tar -x -C '#{stage1_app}'"
-    yield Promise.join download_themes,
-      (async ->
-        yield archive_app
-        yield fs.moveAsync path.join(stage1_app, 'default-config.cson'),
-          path.join(stage1_app, 'config.cson')
-        yield Promise.join(
-          translateCoffeeAsync(stage1_app),
-          (async ->
-            yield npmInstallAsync npm_path, stage1_app
-            yield bowerInstallAsync bower_path, stage1_app
-            )())
-        )()
-
-    # Prepare stage2
-    yield filterCopyAppAsync stage1_app, stage2_app
-
-    # Pack app.asar
-    app_path = yield packageAppAsync poi_version, stage2_app, release_dir
-
-    # Prepare stage 3
-    stage3_info = yield Promise.all (
-      for [platform, arch] in platform_arch_list
-        packageStage3Async(platform, arch, poi_version, electron_version,
-          download_dir, app_path, building_root, release_dir))
-
-    # Finishing work of stage 3
-    stage3_logs = (for [[platform, arch], info] in _.zip(
-      platform_arch_list, stage3_info) when info
-      ["#{platform}-#{arch}", info.log])
-    if stage3_logs
+# Download dependencies in order to run ``electron .`` right at the poi repo
+module.exports.buildLocalAsync = ->
+  download_dir = path.join __dirname, build_dir_name, download_dir_name
+  theme_root = path.join __dirname, 'assets', 'themes'
+  flash_dir = path.join __dirname, 'PepperFlash'
+  npm_path = 'npm'
+  bower_path = path.join(__dirname, 'node_modules', '.bin', 'bower')
+
+  download_theme = downloadThemesAsync theme_root, download_dir
+  install_flash = installFlashAsync os.platform(), os.arch(), download_dir,
+    flash_dir
+  install_npm_bower = npmInstallAsync npm_path, __dirname
+  .then -> (async -> yield bowerInstallAsync bower_path, __dirname)()
+
+  Promise.join download_theme, install_flash, install_npm_bower
+
+
+# Package release archives of poi, on multiple platforms
+module.exports.buildAsync = async (poi_version, electron_version, platform_arch_list) ->
+  async (poi_version, electron_version, platform_arch_list) ->
+  build_root = path.join __dirname, build_dir_name
+
+  download_dir = path.join build_root, download_dir_name
+  building_root = path.join build_root, "building_#{poi_version}"
+  release_dir = path.join build_root, release_dir_name
+
+  stage1_app = path.join building_root, 'stage1'
+  stage2_app = path.join building_root, 'app'
+
+  theme_root = path.join stage1_app, 'assets', 'themes'
+
+  npm_path = 'npm'
+  bower_path = path.join(__dirname, 'node_modules', '.bin', 'bower')
+
+  try
+    yield Promise.join \
+      (fs.removeAsync stage1_app),
+      (fs.removeAsync stage2_app)
+  catch e
+  fs.ensureDirSync stage1_app
+  fs.ensureDirSync stage2_app
+
+  # Check npm version
+  npm_version = (yield execAsync "'#{npm_path}' --version").trim()
+  log "You are using npm v#{npm_version}"
+  if semver.major(npm_version) == 2
+    log "*** USING npm 2 TO BUILD poi IS PROHIBITED ***"
+    log "Aborted."
+    return
+
+  # Prepare stage1
+  download_themes = downloadThemesAsync theme_root, download_dir
+  archive_app = execAsync "git archive HEAD | tar -x -C '#{stage1_app}'"
+  yield Promise.join download_themes,
+    (async ->
+      yield archive_app
+      yield fs.moveAsync path.join(stage1_app, 'default-config.cson'),
+        path.join(stage1_app, 'config.cson')
+      yield Promise.join(
+        translateCoffeeAsync(stage1_app),
+        (async ->
+          yield npmInstallAsync npm_path, stage1_app
+          yield bowerInstallAsync bower_path, stage1_app
+          )())
+      )()
+
+  # Prepare stage2
+  yield filterCopyAppAsync stage1_app, stage2_app
+
+  # Pack app.asar
+  app_path = yield packageAppAsync poi_version, stage2_app, release_dir
+
+  # Prepare stage 3
+  stage3_info = yield Promise.all (
+    for [platform, arch] in platform_arch_list
+      packageStage3Async(platform, arch, poi_version, electron_version,
+        download_dir, app_path, building_root, release_dir))
+
+  # Finishing work of stage 3
+  stage3_logs = (for [[platform, arch], info] in _.zip(
+    platform_arch_list, stage3_info) when info
+    ["#{platform}-#{arch}", info.log])
+  if stage3_logs
+    log " "
+    log "*** BUILDING IS NOT COMPLETED: See log below ***"
+    log " "
+    for [platform_arch, stage3_log] in stage3_logs when stage3_log
+      log "Info when packaging #{platform_arch}:"
+      for line in stage3_log.split '\n'
+        log "  "+line
       log " "
-      log "*** BUILDING IS NOT COMPLETED: See log below ***"
-      log " "
-      for [platform_arch, stage3_log] in stage3_logs when stage3_log
-        log "Info when packaging #{platform_arch}:"
-        for line in stage3_log.split '\n'
-          log "  "+line
-        log " "
-      log "*** Follow the instructions above and press Enter to finish ***"
-      yield new Promise (resolve) ->
-        process.stdin.once 'data', ->
-          process.stdin.unref()   # Allows the program to terminate
-          resolve()
-      yield Promise.all (info.todo() for info in stage3_info when info.todo)
-    log "All platforms are successfully built."
-
-  cleanTempFiles: async ->
-    build_root = path.join __dirname, build_dir_name
-    try
-      yield fs.accessAsync build_root, fs.R_OK
-    catch e
-      log "Nothing to delete."
-      return
-    subdirs = yield subdirListAsync build_root
-    try
-      yield Promise.all (
-        for [dirname, dirpath] in subdirs when dirname != release_dir_name
-          log "Removed #{dirpath}"
-          fs.removeAsync dirpath)
-    catch
-    log "Done."
+    log "*** Follow the instructions above and press Enter to finish ***"
+    yield new Promise (resolve) ->
+      process.stdin.once 'data', ->
+        process.stdin.unref()   # Allows the program to terminate
+        resolve()
+    yield Promise.all (info.todo() for info in stage3_info when info.todo)
+  log "All platforms are successfully built."
+
+
+# Remove everything in "build" except the archived release
+module.exports.cleanTempFiles = async ->
+  build_root = path.join __dirname, build_dir_name
+  try
+    yield fs.accessAsync build_root, fs.R_OK
+  catch e
+    log "Nothing to delete."
+    return
+  subdirs = yield subdirListAsync build_root
+  try
+    yield Promise.all (
+      for [dirname, dirpath] in subdirs when dirname != release_dir_name
+        log "Removed #{dirpath}"
+        fs.removeAsync dirpath)
+  catch
+  log "Done."


more more

more more

--- app/assets/javascripts/inspector_color.coffee
+++ app/assets/javascripts/inspector_color.coffee
@@ -212,6 +212,7 @@
               intro: "Baby blue would have been commercial space, like a dry goods or grocery store."
               position: "right"
               polygon_index: 0
+              ixactive: true
             }
             {
               element: "#map-highlight"
@@ -221,13 +222,20 @@
             }
             {
             element: "#green-button"
-            intro: "In the app, you can drag the page down over the buttons - which are made from actual parts of the map - to compare and see what the best match is."
+            intro: "In the app, you can drag the page down over the buttons - which are made from actual parts of the map - to see what the best match is."
             position: "right"
             polygon_index: 1
-            }
-            {
-            element: "#buttons .multiple"
-            intro: "If a footprint has more than one color, it could have been a mixed-use building or been made of different materials. Click this check box and you'll be able to select multiple colors."
+            ixactive: true
+            }
+            {
+            element: "#map-highlight"
+            intro: "If a footprint has more than one color, it could have been a mixed-use building or been made of different materials."
+            position: "right"
+            polygon_index: 2
+            }
+            {
+            element: " #buttons .multiple"
+            intro: " Click this check box and you'll be able to select multiple colors."
             position: "top"
             polygon_index: 2
             }


Fix dates for people in wrong timezones.

--- app/assets/javascripts/models/anime.js.coffee
+++ app/assets/javascripts/models/anime.js.coffee
@@ -52,12 +52,12 @@
   formattedAirDates: (->
     if @get('startedAiring')
       format = if @get('startedAiringDateKnown') then "D MMM YYYY" else "MMM YYYY"
-      formattedStartedAiring = moment(@get('startedAiring')).format(format)
+      formattedStartedAiring = moment(@get('startedAiring')).utc().format(format)
     else
       formattedStartedAiring = "?"

     if @get('finishedAiring')
-      formattedFinishedAiring = moment(@get('finishedAiring')).format("D MMM YYYY")
+      formattedFinishedAiring = moment(@get('finishedAiring')).utc().format("D MMM YYYY")
     else
       formattedFinishedAiring = "?"


Pan to location on map on select

Pan to location on map on select

--- app/assets/javascripts/servers/form.js.coffee
+++ app/assets/javascripts/servers/form.js.coffee
@@ -662,12 +662,18 @@

             newContent = generatePopupContent(photo, data)
             photo.marker.bindPopup(newContent)
+
+            unless $.isEmptyObject(selected_location)
+              popUp(selected_location.id)

         else fetchPhotoByCity(city, id, layer).then (photo) ->
           marker = photo.marker
           data   = marker.feature.properties
           newContent = generatePopupContent(photo, data)
           photo.marker.bindPopup(newContent)
+
+          unless $.isEmptyObject(selected_location)
+            popUp(selected_location.id)

         layer.options.riseOnHover = true
         layer.options.title       =
@@ -695,6 +701,19 @@

     clusters.addLayer(geoJsonLayer)
     map.addLayer(clusters)
+
+    popUp = (loc) ->
+      geoJsonLayer.eachLayer (marker) ->
+        if marker?.feature?.properties.id is loc
+          id = marker._leaflet_id
+          map.panTo(marker.getLatLng())
+          marker.bounce({duration: 400, height: 10})
+          map._layers[id].openPopup()
+          resetMarkers(markers)
+          toggleMarkerPin marker.options.icon.options, marker
+
+    $locationField.on 'change', (e) ->
+      popUp($(this).select2('data').id)

     #map.featureLayer.setGeoJSON(geojson)
     return


Fix the norloge and totoz on the board

Fix the norloge and totoz on the board

--- app/assets/javascripts/chat.coffee
+++ app/assets/javascripts/chat.coffee
@@ -10,13 +10,14 @@
       @board.find(".board-left .norloge").click @norloge
       @board.find("form").submit @postMessage
       @totoz_type = $.cookie("totoz-type")
-      @totoz_url = $.cookie("totoz-url") or "http://sfw.totoz.eu/gif/"
+      @totoz_url  = $.cookie("totoz-url") or "http://sfw.totoz.eu/gif/"
       @norlogize right  for right in @board.find(".board-right")
       @board.delegate("time", "mouseenter", @highlitizer)
             .delegate("time", "mouseleave", @deshighlitizer)
       if @totoz_type == "popup"
         @totoz = @board.append($("<div id=\"les-totoz\"/>")).find("#les-totoz")
-        @board.find(".totoz").live("mouseenter", @createTotoz).live("mouseleave", @destroyTotoz)
+        @board.delegate(".totoz", "mouseenter", @createTotoz)
+              .delegate(".totoz", "mouseleave", @destroyTotoz)
       @poll()

     findCursor: ->
@@ -59,7 +60,7 @@
       @inbox.find("time.highlighted").removeClass "highlighted"

     createTotoz: (event) =>
-      totozName = @getAttribute("data-totoz-name")
+      totozName = event.target.getAttribute("data-totoz-name")
       totozId = encodeURIComponent(totozName).replace(/%/g, "")
       totoz = @totoz.find("#totoz-" + totozId).first()
       if totoz.size() == 0
@@ -71,8 +72,8 @@
       [x, y] = [offset.left, offset.top]
       totoz.css "z-index": "15", display: "block", top: y + 20, left: x + 20

-    destroyTotoz: =>
-      totozId = encodeURIComponent(@getAttribute("data-totoz-name")).replace(/%/g, "")
+    destroyTotoz: (event) =>
+      totozId = encodeURIComponent(event.target.getAttribute("data-totoz-name")).replace(/%/g, "")
       totoz = @totoz.find("#totoz-" + totozId).first()
       totoz.css display: "none"

@@ -109,7 +110,7 @@
           if this[method]
             @inbox.prepend(message.msg).find(".board-left:first .norloge").click @norloge
             this[method] message.msg
-          @inbox.find(".board-right:first").each @norlogize
+          @norlogize right for right in @inbox.find(".board-right:first")

     on_chat: ->


make slackReadyFunction available to other scripts

--- app/assets/javascripts/main.coffee
+++ app/assets/javascripts/main.coffee
@@ -37,9 +37,11 @@
   sentByMeMessages()
   addMultilineToMessages()

-TS.groups.message_received_sig.add redraw
-TS.channels.message_received_sig.add redraw
-TS.channels.switched_sig.add redraw
-TS.groups.switched_sig.add redraw
+slackReadyFunction = (func) ->
+  TS.groups.message_received_sig.add func
+  TS.channels.message_received_sig.add func
+  TS.channels.switched_sig.add func
+  TS.groups.switched_sig.add func
+  func()

-redraw()
+slackReadyFunction(redraw)


used wrong this

used wrong this

--- app/assets/javascripts/main.coffee
+++ app/assets/javascripts/main.coffee
@@ -54,10 +54,10 @@
                     aMarker = newMarker.marker
                     aMarker._____refToSelf = newMarker
                     google.maps.event.addListener aMarker, "click", () ->
-                        if this._infoWindow?
-                            this._infoWindow.close()
+                        if self._infoWindow?
+                            self._infoWindow.close()
                         this._____refToSelf.infoWindow.open self._map, this._____refToSelf.marker
-                        this._infoWindow = this._____refToSelf.infoWindow
+                        self._infoWindow = this._____refToSelf.infoWindow
                     self._locations.push newMarker

             # Remove unseen items from list.


move update deduplication to filterAndLimit method

--- app/coffee/MongoPackManager.coffee
+++ app/coffee/MongoPackManager.coffee
@@ -202,18 +202,6 @@
 				# the changeset into parts
 				updates = MongoPackManager._filterAndLimit(updates, extraSet, filterFn, null)
 				#console.log 'extra updates after filterandlimit', updates
-				# remove duplicates
-				seen = {}
-				updates = updates.filter (item) ->
-					key = item.doc_id + ' ' + item.v
-					#console.log 'key is', key
-					if seen[key]
-						return false
-					else
-						seen[key] = true
-						return true
-				#console.log 'extra updates are', updates
-
 			tail.toArray (err, result2) ->
 				if err?
 					return callback err, updates.sort timeOrder
@@ -252,5 +240,15 @@
 		# update results with extra docs, after filtering and limiting
 		filtered = extra.filter(filterFn)
 		newResults = results.concat filtered
+		# remove duplicates
+		seen = {}
+		newResults = newResults.filter (item) ->
+			key = item.doc_id + ' ' + item.v
+			#console.log 'key is', key
+			if seen[key]
+				return false
+			else
+				seen[key] = true
+			return true
 		newResults.slice(0, limit) if limit?
 		return newResults


AppController: Create new pane from change if change type is 'NewPaneCreated'.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -878,7 +878,7 @@

   handleChange: (change) ->
-    {context, origin} = change
+    {context, origin, type} = change
     myWatchMap = @rtm.getFromModel @realTimeDoc, "#{KD.nick()}WatchMap"

     return if not context or not origin or origin is KD.nick()
@@ -886,6 +886,9 @@
     amIWatchingChangeOwner = not myWatchMap or myWatchMap.keys().length is 0 or origin in myWatchMap.keys()

     if amIWatchingChangeOwner
+      if type is 'NewPaneCreated'
+        @createPaneFromChange change
+
       @getPaneByChange(change)?.handleChange? change, @rtm, @realTimeDoc


IDE: Code refactor.

IDE: Code refactor.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -381,20 +381,18 @@
     menuButton.show()

   showFileFinder: ->
-    if @fileFinder
-      @fileFinder.input.setFocus()
-    else
-      @fileFinder = new IDE.FileFinder
-      @fileFinder.once 'KDObjectWillBeDestroyed', => @fileFinder = null
+    return @fileFinder.input.setFocus()  if @fileFinder
+
+    @fileFinder = new IDE.FileFinder
+    @fileFinder.once 'KDObjectWillBeDestroyed', => @fileFinder = null

   showContentSearch: ->
-    if @contentSearch
-      @contentSearch.findInput.setFocus()
-    else
-      @contentSearch = new IDE.ContentSearch
-      @contentSearch.once 'KDObjectWillBeDestroyed', => @contentSearch = null
-      @contentSearch.once 'ViewNeedsToBeShown', (view) =>
-        @activeTabView.emit 'ViewNeedsToBeShown', view
+    return @contentSearch.findInput.setFocus()  if @contentSearch
+
+    @contentSearch = new IDE.ContentSearch
+    @contentSearch.once 'KDObjectWillBeDestroyed', => @contentSearch = null
+    @contentSearch.once 'ViewNeedsToBeShown', (view) =>
+      @activeTabView.emit 'ViewNeedsToBeShown', view

   createStatusBar: (splitViewPanel) ->
     splitViewPanel.addSubView @statusBar = new IDE.StatusBar


hotfix: autocomplete of circuit pins

hotfix: autocomplete of circuit pins

--- client/src/app/util/pins.coffee
+++ client/src/app/util/pins.coffee
@@ -33,4 +33,9 @@
     for channel in channels
       keys.push("io.sound.#{channel.name}")

-    return keys+    # Fill in circuit pins
+    pins = currentGame.version.circuits[circuits.currentCircuitMeta.type].pinDefs || {}
+    for pinName, pinDef of pins
+      keys.push("circuit.#{pinName}")
+
+    return keys


Trying out sorting roads by camera count.

--- controllers/websockets.coffee
+++ controllers/websockets.coffee
@@ -33,6 +33,6 @@

 # Return all available geocode road names as a list.
 msg roadList: ->
-    app.RoadModel.find {}, (err, docs) ->
+    app.RoadModel.find().sort(['cameraCount'], -1).run (err, docs) ->
         if err then send 'message', text: JSON.stringify err
         else send 'roadList', roadList: docs


Initial version of passing arguments to a component.

--- lib.coffee
+++ lib.coffee
@@ -33,7 +33,7 @@
         Blaze._warn "Assigning helper with `" + template.viewName + "." + name + " = ...` is deprecated.  Use `" + template.viewName + ".helpers(...)` instead."
     return template[name]

-  # TODO: Can we simply ignore reactivity here? Can this template instance or parent template instances change without reconstructing the component as well? I don't think so. Only data context is changing and this is why templateInstance or .get() are reactive and we do not care about data context here.
+  # TODO: Blaze.View::lookup should not introduce any reactive dependencies. Can we simply ignore reactivity here? Can this template instance or parent template instances change without reconstructing the component as well? I don't think so. Only data context is changing and this is why templateInstance or .get() are reactive and we do not care about data context here.
   component = Tracker.nonreactive ->
     templateInstance = templateInstance()
     templateInstance.get 'component'
@@ -86,7 +86,9 @@
   return

 Blaze._getComponent = (componentName) ->
-  BlazeComponent.getComponent(componentName)?.renderComponent() or null
+  # Blaze.View::lookup should not introduce any reactive dependencies, so we are making sure.
+  Tracker.nonreactive =>
+    BlazeComponent.getComponent(componentName)?.renderComponent() or null

 createUIHooks = (component, parentNode) ->
   insertElement: (node, before) =>
@@ -174,6 +176,22 @@
       templateBase = componentClassTemplate
       assert templateBase

+    try
+      data = Template.currentData()
+    catch error
+      # The exception can be thrown when there is no current view which happens when
+      # there is no data context yet, thus also no arguments were provided through
+      # "args" template helper, so we just continue normally.
+      data = null
+
+    if data?.constructor is argumentsConstructor
+      argumentsProvided = true
+      # Arguments were provided through "args" template helper, use them with the constructor.
+      component = new componentClass data._arguments...
+    else
+      argumentsProvided = false
+      component = new componentClass()
+
     # Create a new component template based on the Blaze template. We want our own template
     # because the same Blaze template could be reused between multiple components.
     # TODO: Should we cache these templates based on (componentName, templateBase) pair? We could use tow levels of ES6 Maps, componentName -> templateBase -> template.
@@ -182,12 +200,13 @@
     # We on purpose do not reuse helpers, events, and hooks. Templates are used only for HTML rendering.

     template.onCreated ->
+      # @ is a template instance.
+
       @view._onViewRendered =>
         # Attach events the first time template instance renders.
         addEvents @view, @component if @view.renderCount is 1

-      # @ is a template instance.
-      @component = new componentClass()
+      @component = component
       @component.templateInstance = @
       @component.onCreated()

@@ -199,7 +218,12 @@
       # @ is a template instance.
       @component.onDestroyed()

-    template
+    return template unless argumentsProvided
+
+    # Arguments were provided through "args" template helper and passed in as a data
+    # context. Restore original (parent) data context and render the component in it.
+    new Blaze.Template ->
+      Blaze._TemplateWith (-> Template.parentData()), (-> template)

   @template: ->
     # You have to override this method with a method which returns a template name or template itself.
@@ -244,3 +268,14 @@
 for methodName, method of Blaze.TemplateInstance::
   BlazeComponent::[methodName] = (args...) ->
     @templateInstance[methodName] args...
+
+argumentsConstructor = ->
+  # This class should never really be created.
+  assert false
+
+Template.registerHelper 'args', ->
+  obj = {}
+  # We use custom constructor to know that it is not a real data context.
+  obj.constructor = argumentsConstructor
+  obj._arguments = arguments
+  obj


Test current element too.

Test current element too.

--- foreground.coffee
+++ foreground.coffee
@@ -29,7 +29,6 @@
         unless event.repeat
           element = @element ? document.activateElement
           element ?= @querySelector document.body, @config.activeSelector if @config.activeSelector
-          console.log "pick", element
           @activateElement element, event if element and Common.isInViewport element

       when "up", "down"
@@ -212,7 +211,6 @@

   activateElement: (element, event) ->
     activate = (ele = element) =>
-      console.log "activate", ele
       unless @config.noclick
         if ele.tagName.toLowerCase() == "a" and /^(http|https|file):\/\//.test ele.href
           # These lines we open ourselves.
@@ -228,6 +226,9 @@
       selectors = [ "a[target=_blank]", "a[href~=http", "a[href~=https]", "a" ]
       selectors = [ (Common.stringToArray @config.activators)..., selectors... ] if @config.activators
       for selector in selectors
+        if element.matches selector
+          activate element
+          return
         if candidate = @querySelector element, selector
           activate candidate
           return


More better UX around scrolling (fix).

More better UX around scrolling (fix).

--- foreground.coffee
+++ foreground.coffee
@@ -65,9 +65,9 @@
       if 0 <= oldIndex < newIndex
         if Common.canScroll action
           { top, bottom } = @element.getBoundingClientRect()
-          if action == "up" and top + delta < @config.offset and Common.canScroll "up"
+          if action == "up" and top + delta < @config.offset
             newIndex = oldIndex
-          else if action == "down" and @config.offset + delta < top and Common.canScroll "up"
+          else if action == "down" and @config.offset + delta < top
             newIndex = oldIndex

       @selectElement elements[newIndex]


Disable DOCS urls until there is something for them to point to

--- compiler/SDKError.coffee
+++ compiler/SDKError.coffee
@@ -18,30 +18,32 @@

 MAIN = 'http://docs.marquee.by/marquee-static-sdk/'

-DOCS =
-    main                    : MAIN
-    configuration           : "#{ MAIN }configuration/"
-    'configuration.deploy'  : "configuration.deploy"
-    tokens                  : "#{ MAIN }tokens/"
-    emitFile                : "#{ MAIN }emitFile/"
-    'emitFile.json'         : ''
-    'project.react'         : ''
-    files                   : "#{ MAIN }files/"
-    assets                  : "#{ MAIN }assets/"
-    compiler                : "#{ MAIN }compiler/"
-    'deploy.repo'           : "#{ MAIN }deploy/"
-    api:
-        404: "#{ MAIN }api/not-found/"
-        401: "#{ MAIN }api/unauthorized/"
-        403: "#{ MAIN }api/forbidden/"
-        toString: -> "#{ MAIN }api/"
+DOCS = {}
+# Disable until they're actually available
+# DOCSX =
+#     main                    : MAIN
+#     configuration           : "#{ MAIN }configuration/"
+#     'configuration.deploy'  : "configuration.deploy"
+#     tokens                  : "#{ MAIN }tokens/"
+#     emitFile                : "#{ MAIN }emitFile/"
+#     'emitFile.json'         : ''
+#     'project.react'         : ''
+#     files                   : "#{ MAIN }files/"
+#     assets                  : "#{ MAIN }assets/"
+#     compiler                : "#{ MAIN }compiler/"
+#     'deploy.repo'           : "#{ MAIN }deploy/"
+#     api:
+#         404: "#{ MAIN }api/not-found/"
+#         401: "#{ MAIN }api/unauthorized/"
+#         403: "#{ MAIN }api/forbidden/"
+#         toString: -> "#{ MAIN }api/"

 SDKError = (subject, message, code=null) ->
     if arguments.length is 1
         return new Error(_prefix + colors.error(subject))
     unless DOCS[subject]
-        console.warn(colors.warn("Unknown error subject specified: #{ subject }"))
+        console.warn(colors.grey("Unknown error subject specified: #{ subject }"))
         return new Error(_prefix + colors.error(message))
     if code and DOCS[subject][code]
         url = DOCS[subject][code]


HTML scraping component is now in noflo-html, refs #49

--- test/ScrapeHtml.coffee
+++ test/ScrapeHtml.coffee
@@ -1,84 +0,0 @@
-scrape = require "../src/components/ScrapeHtml"
-socket = require "../src/lib/InternalSocket"
-
-setupComponent = ->
-    c = scrape.getComponent()
-    ins = socket.createSocket()
-    out = socket.createSocket()
-    c.inPorts.in.attach ins
-    c.outPorts.out.attach out
-    return [c, ins, out]
-
-exports["test selector then html"] = (test) ->
-    [c, ins, out] = setupComponent()
-    s = socket.createSocket()
-    c.inPorts.textSelector.attach s
-    expect = ["bar","baz"]
-    out.once "begingroup", (group) ->
-        test.fail "should not get groups without element ids"
-    out.on "data", (data) ->
-        test.equal data, expect.shift()
-        test.done() if expect.length == 0
-    s.send "p.test"
-    s.disconnect()
-    ins.send '

foo

ba'
-    ins.send 'r

baz

'
-    ins.disconnect()
-
-exports["test html then selector"] = (test) ->
-    [c, ins, out] = setupComponent()
-    s = socket.createSocket()
-    c.inPorts.textSelector.attach s
-    expect = ["bar","baz"]
-    out.on "data", (data) ->
-        test.equal data, expect.shift()
-        test.done() if expect.length == 0
-    ins.send '

foo

ba'
-    ins.send 'r

baz

'
-    ins.disconnect()
-    s.send "p.test"
-    s.disconnect()
-
-exports["test ignore"] = (test) ->
-    [c, ins, out] = setupComponent()
-    s = socket.createSocket()
-    i = socket.createSocket()
-    c.inPorts.textSelector.attach s
-    c.inPorts.ignoreSelector.attach i
-    expect = ["foo"]
-    out.on "data", (data) ->
-        test.equal data, expect.shift()
-        test.done() if expect.length == 0
-    i.send ".noise"
-    i.send "#crap"
-    i.disconnect()
-    ins.send '

foo

ba'
-    ins.send 'r

baz

'
-    ins.disconnect()
-    s.send "p.test"
-    s.disconnect()
-
-exports["test group by element id"] = (test) ->
-    [c, ins, out] = setupComponent()
-    s = socket.createSocket()
-    c.inPorts.textSelector.attach s
-    expectevent = "begingroup"
-    expectgroup = ["a","b"]
-    out.on "begingroup", (group) ->
-        test.equal "begingroup", expectevent
-        test.equal group, expectgroup.shift()
-        expectevent = "data"
-    expectdata = ["bar","baz"]
-    out.on "data", (data) ->
-        test.equal "data", expectevent
-        test.equal data, expectdata.shift()
-        expectevent = "endgroup"
-    out.on "endgroup", ->
-        test.equal "endgroup", expectevent
-        expectevent = "begingroup"
-        test.done() if expectgroup.length == 0
-    s.send "p.test"
-    s.disconnect()
-    ins.send '

foo

ba'
-    ins.send 'r

baz

'
-    ins.disconnect()


Remove timeout

Remove timeout

--- test/js/totodoo.coffee
+++ test/js/totodoo.coffee
@@ -33,4 +33,3 @@
 				text = drvr.findElement(id: 'listName').getText()
 				expect(text).to.eventually.equal
 				'Public list 1'
-				@timeout 4000


Updated search to use promises to handle async calls

--- app/webroot/coffee/views/search/search.coffee
+++ app/webroot/coffee/views/search/search.coffee
@@ -89,7 +89,7 @@
   setupSearch: ->
     @scrollReady = false
     @search = new arcs.utils.Search
-      container: $('search-box')
+      container: $('searchBox')
       order: @options.sort
       run: false
       loader: true
@@ -236,47 +236,48 @@

   #Activates on enter press: search
   $ ->
-    $("#search-container").submit (e) ->
-      e.preventDefault()
-      #resources search
-      val = $("#search-box").val()
-      resources = new Promise((resolve, reject) ->
-        resourcequery = encodeURIComponent("(Type,like,#{val}),or,(Resource Identifier,like,#{val}),or,(Earliest Date,like,#{val}),or,(Latest Date,like,#{val})")
-        req = $.getJSON arcs.baseURL + "resources/search?q=#{resourcequery}&sid=736", (response) ->
-          resolve(response)
-      )
-      projects = new Promise((resolve, reject) ->
-        projectquery = encodeURIComponent("(Country,like,#{val})")
-        req = $.getJSON arcs.baseURL + "resources/search?q=#{projectquery}&sid=734", (response) ->
-          resolve(response)
-      )
-      seasons = new Promise((resolve, reject) ->
-        seasonquery = encodeURIComponent("(Title,like,#{val}),or,(Description,like,#{val}),or,(Earliest Date,like,#{val}),or,(Latest Date,like,#{val})")
-        req = $.getJSON arcs.baseURL + "resources/search?q=#{seasonquery}&sid=735", (response) ->
-          resolve(response)
-      )
-      excavations = new Promise((resolve, reject) ->
-        excavationquery = encodeURIComponent("(Name,like,#{val}),or,(Earliest Date,like,#{val}),or,(Latest Date,like,#{val})")
-        req = $.getJSON arcs.baseURL + "resources/search?q=#{excavationquery}&sid=740", (response) ->
-          resolve(response)
-      )
-      observations = new Promise((resolve, reject) ->
-        observationquery = encodeURIComponent("(Monument Classification,like,#{val}),or,(Monument.Type,like,#{val}),or,(Monument.Material,like,#{val}),or,(Monument.Technique,like,#{val}),or,(Monument.Period,like,#{val}),or,(Monument.Terminus Ante Quem,like,#{val}),or,(Monument.Terminus Post Quem,like,#{val})")
-        req = $.getJSON arcs.baseURL + "resources/search?q=#{observationquery}&sid=739", (response) ->
-          resolve(response)
-      )
-
-      totalResults = {}
-      Promise.all([resources,projects,seasons,excavations,observations]).then((values) ->
-        #console.log(values)
+    $(".searchBoxInput").keyup (e) ->
+      if e.keyCode == 13
+        e.preventDefault()
+        #resources search
+        val = $(".searchBoxInput").val()
+        console.log(val)
+        resources = new Promise((resolve, reject) ->
+          resourcequery = encodeURIComponent("(Type,like,#{val}),or,(Resource Identifier,like,#{val}),or,(Earliest Date,like,#{val}),or,(Latest Date,like,#{val})")
+          req = $.getJSON arcs.baseURL + "resources/search?q=#{resourcequery}&sid=736", (response) ->
+            resolve(response)
+        )
+        projects = new Promise((resolve, reject) ->
+          projectquery = encodeURIComponent("(Country,like,#{val})")
+          req = $.getJSON arcs.baseURL + "resources/search?q=#{projectquery}&sid=734", (response) ->
+            resolve(response)
+        )
+        seasons = new Promise((resolve, reject) ->
+          seasonquery = encodeURIComponent("(Title,like,#{val}),or,(Description,like,#{val}),or,(Earliest Date,like,#{val}),or,(Latest Date,like,#{val})")
+          req = $.getJSON arcs.baseURL + "resources/search?q=#{seasonquery}&sid=735", (response) ->
+            resolve(response)
+        )
+        excavations = new Promise((resolve, reject) ->
+          excavationquery = encodeURIComponent("(Name,like,#{val}),or,(Earliest Date,like,#{val}),or,(Latest Date,like,#{val})")
+          req = $.getJSON arcs.baseURL + "resources/search?q=#{excavationquery}&sid=740", (response) ->
+            resolve(response)
+        )
+        observations = new Promise((resolve, reject) ->
+          observationquery = encodeURIComponent("(Monument Classification,like,#{val}),or,(Monument.Type,like,#{val}),or,(Monument.Material,like,#{val}),or,(Monument.Technique,like,#{val}),or,(Monument.Period,like,#{val}),or,(Monument.Terminus Ante Quem,like,#{val}),or,(Monument.Terminus Post Quem,like,#{val})")
+          req = $.getJSON arcs.baseURL + "resources/search?q=#{observationquery}&sid=739", (response) ->
+            resolve(response)
+        )

-        for item in values
-          Array::push.apply totalResults, item.results
-        #console.log(totalResults)
-        Search.prototype._render results: totalResults
-        $('#search-pagination').html arcs.tmpl('search/paginate', results: totalResults)
-      )
-
+        totalResults = {}
+        Promise.all([resources,projects,seasons,excavations,observations]).then((values) ->
+          #console.log(values)
+
+          for item in values
+            Array::push.apply totalResults, item.results
+          #console.log(totalResults)
+          Search.prototype._render results: totalResults
+          $('#search-pagination').html arcs.tmpl('search/paginate', results: totalResults)
+        )

       #projects search
       #resourcequery = encodeURIComponent("(Type,like,Pho),or,(Type,like,Note)")
@@ -310,7 +311,6 @@
   _render: (results, append=false) ->
     $results = $('#search-results')
     template = if @options.grid then 'search/grid' else 'search/list'
-    console.log(results)
     results = results.results
     console.log(results)
     $results[if append then 'append' else 'html'] arcs.tmpl(template, results: results)


set title element to buttons

set title element to buttons

--- app/js/util/factlink_button.coffee
+++ app/js/util/factlink_button.coffee
@@ -1,10 +1,18 @@
 Factlink.initializeFactlinkButton = ->
-  $(document).on "click", ".js-factlink-create-button", (e) ->
+  buttonSel = ".js-factlink-create-button"
+
+  $(document).on "click", buttonSel, (e) ->
     e.preventDefault()
     e.stopPropagation()
     Factlink.triggerClick()

-  $(document).on "mousedown", ".js-factlink-create-button", (e) ->
+  $(document).on "mousedown", buttonSel, (e) ->
     # prevent mousedown from deselecting the text that
     # the button is intended to use:
     e.preventDefault()
+
+  setTitle = ->
+    $(buttonSel)
+      .attr('title', 'Select any statement in this article and click this button to add your opinion.')
+      .length > 0
+  setTitle() || $ setTitle


Fixed module check

Fixed module check

The module check was inaccurate - the existential operator in Coffeescript (`?`) will return `true` if the value is anything other than `null` or `undefined`. This means `hasModule?` will return true, even if `hasModule === false`.

I also removed the `typeof` checks in the `hasModule` definition - that's what the existential operator is good at :)

--- src/moment.twitter.coffee
+++ src/moment.twitter.coffee
@@ -1,5 +1,5 @@
-hasModule = typeof module isnt 'undefined' and module.exports and typeof require isnt 'undefined'
-if hasModule?
+hasModule = module?.exports? and require?
+if hasModule
     moment = require 'moment'
 else
     moment = @moment


Fix tests

Fix tests

--- tests/iterator-test.coffee
+++ tests/iterator-test.coffee
@@ -11,8 +11,10 @@
   iterator = null
   value = 'Hello World'

-  it 'should open database', ->
-    db = leveldb.openSync filename, create_if_missing: true
+  it 'should open database', (done) ->
+    leveldb.open filename, create_if_missing: true, (err, handle) ->
+      db = handle
+      done()

   it 'should insert batch data', ->
     batch = new leveldb.Batch
@@ -53,12 +55,12 @@
   it 'should not be valid', ->
     assert.ifError iterator.valid()

-  it 'should seek to key 200', (done) ->
-    iterator.seek '200', done
+  it 'should seek to last key', (done) ->
+    iterator.last done

   it 'should get values in reverse', (done) ->
     testNext = (i) ->
-      iterator.next (err, key, val) ->
+      iterator.prev (err, key, val) ->
         assert.ifError err
         assert.equal "#{i}", key
         assert.equal value, val
@@ -74,7 +76,6 @@

   it 'should close database', (done) ->
     db.close done
-    db = null

   it 'should not get values', ->
     assert.throws -> iterator.first()
@@ -111,8 +112,8 @@
   it 'should not be valid', ->
     assert.ifError iterator.valid()

-  it 'should seek to key 200', ->
-    iterator.seekSync '200'
+  it 'should seek last key', ->
+    iterator.lastSync()

   it 'should get values in reverse', ->
     for i in [200..100]


Make speed test wimpy.

Make speed test wimpy.

--- test/speed_test.coffee
+++ test/speed_test.coffee
@@ -26,5 +26,5 @@
       dt = new Date().getTime() - @start_time
       @get @requested, (val) =>
         @assert.equal val, big_num
-        @assert.equal true, (dt < 2000)
+        @assert.equal true, (dt < 10000)
         @finish()


db/change_email: fix missing opts.*

db/change_email: fix missing opts.*

--- src/packages/database/postgres-server-queries.coffee
+++ src/packages/database/postgres-server-queries.coffee
@@ -1363,7 +1363,7 @@
                             return
                         cb()
             (cb) =>
-                checkEmailExclusiveSSO @, opts.account_id, email_address, (err, exclusive) =>
+                checkEmailExclusiveSSO @, opts.account_id, opts.email_address, (err, exclusive) =>
                     if err
                         cb(err)
                         return


a different approach

a different approach

--- exifexodus.coffee
+++ exifexodus.coffee
@@ -15,122 +15,131 @@
 headerSize = 'data:image/jpeg;base64,'.length
 cleaned    = {}
 frMethods  = {}
+xhrSend    = XMLHttpRequest::send
 fdAppend   = FormData::append
+

 for method in ['readAsDataURL', 'readAsArrayBuffer', 'readAsBinaryString', 'readAsText']
   do (method) ->
     frMethods[method] = FileReader::[method]
     FileReader::[method] = (file, enc) ->
-      unless file[ns]
+      unless file.type is jpgType
         return frMethods[method].call @, file

-      fn = => frMethods[method].call @, cleaned[file[ns]].blob, enc
+      cleanImage file, (blob) => frMethods[method].call @, blob, enc

-      if file[ns + 'ready']
-        fn()
+
+XMLHttpRequest::send = (data) ->
+  if data instanceof FormData and data[ns]
+    jpgs     = []
+    formData = new FormData
+    for key, val of data[ns]
+      if val.file and val.file instanceof File
+        jpgs.push val
       else
-        cleaned[file[ns]].queue.push fn
+        fdAppend.call formData, key, val
+
+    if jpgs.length
+      toClean = jpgs.length
+      for jpg in jpgs then do (jpg) =>
+        cleanImage jpg.file, (blob) =>
+          fdAppend.call formData, jpg.name, blob, jpg.filename
+          unless --toClean
+            xhrSend.call @, formData
+
+      undefined
+
+    else
+      xhrSend.apply @, arguments
+
+  else
+    xhrSend.apply @, arguments
+

 FormData::append = (key, val, filename) ->
-  if val instanceof File and val[ns]
-    fdAppend.call @, key, cleaned[file[ns]].blob, filename
+  @[ns] or= {}
+  @[ns][key] = if val instanceof File
+    obj = {}
+    obj[ns] = {name: key, file: val, filename}
   else
-    fdAppend.apply @, arguments
+    val
+
+  fdAppend.apply @, arguments

-onChange = (e) ->
-  input      = e.target
-  formMarked = false
+HTMLFormElement::submit = -> onSubmit target: @

-  return true if input.type isnt 'file'

-  for file in input.files then do (file) ->
-    return if file.type isnt jpgType
-    nonce  = (Math.random() * 1e16).toString 36
-    reader = new FileReader
+cleanImage = (file, cb) ->
+  reader = new FileReader
+  reader.addEventListener 'load', ->
+    img = new Image
+    img.addEventListener 'load', ->
+      {width, height} = img
+      canvas          = document.createElement 'canvas'
+      canvas.width    = width
+      canvas.height   = height

-    unless formMarked
-      formMarked = true
-      parent     = input.parentNode
-      loop
-        break unless parent
-        if parent.tagName.toLowerCase() is 'form'
-          parent.dataset[ns] = true
-          break
+      canvas.getContext('2d').drawImage img, 0, 0, width, height
+      canvas.getContext('2d').fillStyle = 'rgb(255,0,255)'
+      canvas.getContext('2d').fillRect 0, 0, width, height

-        parent = parent.parentNode
+      binary  = atob canvas.toDataURL(jpgType, jpgQual)[headerSize...]
+      len     = binary.length
+      view    = new Uint8Array new ArrayBuffer len
+      view[i] = binary.charCodeAt i for i in [0...len]

-    reader.addEventListener 'load', (e) ->
-      img = new Image
-      img.addEventListener 'load', ->
-        {width, height}   = img
-        canvas            = document.createElement 'canvas'
-        canvas.width      = width
-        canvas.height     = height
-        input.dataset[ns] = nonce
+      cb new Blob [view.buffer], type: jpgType

-        canvas.getContext('2d').drawImage img, 0, 0, width, height
+    img.src = reader.result

-        binary  = atob canvas.toDataURL(jpgType, jpgQual)[headerSize...]
-        len     = binary.length
-        view    = new Uint8Array new ArrayBuffer len
-        view[i] = binary.charCodeAt i for i in [0...len]
-
-        cleaned[nonce].blob = new Blob [view.buffer], type: jpgType
-        file[ns + 'ready']  = true
-        fn() for fn in cleaned[nonce].queue
-
-      img.src = reader.result
-
-    cleaned[nonce]     = queue: [], name: file.name
-    file[ns]           = nonce
-    file[ns + 'ready'] = false
-    frMethods.readAsDataURL.call reader, file
+  frMethods.readAsDataURL.call reader, file

 onSubmit = (e) ->
-  form = e.target
-  return true unless form.dataset[ns]
-  e.preventDefault()
-  e.stopPropagation()
+  form     = e.target
+  inputs   = form.querySelectorAll 'input'
+  jpgs     = []
+  formData = new FormData
+
+  for input in inputs when input.value
+    if input.type is 'file'
+      for file in input.files
+        jpgs.push {file, name: input.name} if file.type is jpgType
+    else
+      fdAppend.call formData, input.name, input.value
+
+
+  if jpgs.length
+    e.preventDefault()
+    e.stopImmediatePropagation()

   if !form.action
     return reportErr 'Can\'t proceed, the upload form has no action URL.'

-  formData = new FormData
-  inputs   = form.querySelectorAll 'input'
+  toClean = jpgs.length

-  for input in inputs when input.value
-    if input.dataset[ns]
-      val      = cleaned[input.dataset[ns]].blob
-      filename = cleaned[input.dataset[ns]].name
-    else
-      val = input.value
+  for jpg in jpgs then do (jpg) ->
+    cleanImage jpg.file, (blob) ->
+      fdAppend.call formData, jpg.name, blob, jpg.file.name
+      unless --toClean
+        xhr = new XMLHttpRequest

-    formData.append input.name, val, filename
+        xhr.onreadystatechange = ->
+          document.write xhr.response if xhr.readyState is 4

-  xhr = new XMLHttpRequest
-  xhr.onreadystatechange = (e) ->
-    if xhr.readyState is 4
-      if xhr.responseURL
-        window.location = xhr.responseURL
-      else
-        document.write xhr.response
+        xhr.onerror = ->
+          reportErr 'Something went wrong submitting the upload form.'

-  xhr.onerror = ->
-    reportErr 'Something went wrong submitting the upload form.'
-
-  xhr.open form.method or 'GET', form.action
-  xhr.send formData
+        xhr.open form.method or 'GET', form.action
+        xhrSend.call xhr, formData

 reportErr = (msg) -> alert 'ExifExodus: ' + msg

-init = ->
-  addEventListener 'change', onChange, true
-  addEventListener 'submit', onSubmit, true
+init = -> addEventListener 'submit', onSubmit, true

 if document.readyState is 'complete'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/clamp.coffee
+++ src/clamp.coffee
@@ -4,7 +4,7 @@

   self.bind "update", ->
     for property in I.clamps
-      property.
+      property

       {approachBy, target} = cooldownOptions


adding docs for Color

adding docs for Color

--- src/color.coffee
+++ src/color.coffee
@@ -3387,6 +3387,14 @@
   ###*
   returns a random color.

+

+  Color.random().toString()
+  # => 'rgba(213, 144, 202, 1)'
+
+  Color.random().toString()
+  # => 'rgba(1, 211, 24, 1)'
+

+
   @name random
   @methodOf Color


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

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

     if arguments.length == 2
       parsedColor = [parseInt(arguments[0][0]), parseInt(arguments[0][1]), parseInt(arguments[0][2]), parseFloat(arguments[1])]
-    if arguments.length > 2
+    else if arguments.length > 2
       alpha = if arguments[3]? then arguments[3] else 1
       parsedColor = [parseInt(arguments[0]), parseInt(arguments[1]), parseInt(arguments[2]), parseFloat(alpha)]
     else if color.length > 2 && Object.prototype.toString.call(color) == '[object Array]'


add multiple concurrent CSS animation support

--- scripts/jquery/css_animation.coffee
+++ scripts/jquery/css_animation.coffee
@@ -1,4 +1,4 @@
-styleProperties =
+jQuery.styleProperties =
   name: 'webkitAnimationName'
   delay: 'webkitAnimationDelay'
   direction: 'webkitAnimationDirection'
@@ -7,7 +7,7 @@
   fillStyle: 'webkitAnimationFillStyle'
   iterations: 'webkitAnimationIterationCount'

-eventNames =
+jQuery.eventNames =
   animationEnd: 'webkitAnimationEnd'

 jQuery.cssAnimations =
@@ -22,17 +22,34 @@
   else
     time

+compileAnimations = (animations) ->
+  {}.tap (css) ->
+    for own propertyName, styleName of jQuery.styleProperties
+      css[styleName] = (
+        for own name, animation of animations
+          value = animation[propertyName]
+          if propertyName == 'delay' or propertyName is 'duration'
+            makeCSSTime value, 0
+          else
+            value ? 'initial'
+      ).join ', '
+
+jQuery.event.props.push 'animationName'
+
 jQuery.fn.cssFadeOut = (speed, easing, callback) ->
   {fadeIn, fadeOut} = jQuery.cssAnimations
+  {animationEnd} = jQuery.eventNames

   hideCallback = (event) ->
-    if event.originalEvent.animationName is fadeOut
-      $(this).css(display: 'none').unbind(event.type, hideCallback)
+    if event.animationName is fadeOut
+      $(this)
+      .css(display: 'none')
+      .unbind(event.type, hideCallback)

   $(this)
   .cssStop(fadeIn)
   .cssAnimate(fadeOut, speed, easing, callback)
-  .bind(eventNames.animationEnd, hideCallback)
+  .bind(animationEnd, hideCallback)

 jQuery.fn.cssFadeIn = (speed, easing, callback) ->
   {fadeIn, fadeOut} = jQuery.cssAnimations
@@ -42,30 +59,44 @@
   .cssAnimate(fadeIn, speed, easing, callback)

 jQuery.fn.cssStop = (animationName) ->
-  # TODO actually do this once multiple concurrent animations are supported
-  this
+  {animationEnd} = jQuery.eventNames
+  $(this).each (i, el) ->
+    el = $(el)
+    if cssAnimations = el.data('cssAnimations')
+      animation = cssAnimations[animationName]
+      delete cssAnimations[animationName]
+
+      if animation.onComplete?
+        # trigger animation end callback
+        ev = jQuery.Event animationEnd,
+          animationName: animation.name
+        el.trigger ev
+
+      el.data({cssAnimations}).css(compileAnimations cssAnimations)

-jQuery.fn.cssAnimate = (animationName, speed, easing, callback) ->
+jQuery.fn.cssAnimate = (name, speed, easing, callback) ->
+  {animationEnd} = jQuery.eventNames
+
   optall = jQuery.speed speed, easing, callback
-  optall.duration = makeCSSTime optall.duration
-  optall.delay    = makeCSSTime optall.delay, 0

-  animation = {}
-  noAnimation = {}
+  animation = { name }
+  animation.duration = makeCSSTime optall.duration
+  animation.delay    = makeCSSTime optall.delay, 0

-  realCallback = (event) ->
-    $(this).unbind event.type, realCallback
-    if event.originalEvent.animationName is animationName
-      switch event.type
-        when eventNames.animationEnd
-          $(this).css noAnimation
-          optall.old.call this, event if typeof optall.old is 'function'
+  if typeof optall.old is 'function'
+    callback = optall.old
+  else
+    callback = null

-  animation[styleProperties.name] = animationName
-  noAnimation[styleProperties.name] = ''
+  animation.onComplete = (event) ->
+    if event.animationName is animation.name
+      $(this).unbind(event.type, animation.onComplete)
+      callback?.call this, event

-  for optionName, styleName of styleProperties when optall[optionName]?
-    animation[styleName] = "#{optall[optionName]}"
-    noAnimation[styleName] = ''
-
-  $(this).bind(eventNames.animationEnd, realCallback).css(animation)
+  this.each (i, el) ->
+    cssAnimations = $(el).data('cssAnimations') ? {}
+    cssAnimations[animation.name] = animation
+    $(el)
+    .data({cssAnimations})
+    .css(compileAnimations cssAnimations)
+    .bind(animationEnd, animation.onComplete)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -999,16 +999,25 @@
     ###*
     A copy of the calling color with its alpha increased by `amount`.

-

-    color = Color(0, 0, 0, 0)
+

+    color = Color(0, 0, 0, 0.25)

     color.a
-    # => 1
-
-    opaqueColor = color.opacify(0.25)
+    # => 0.25
+
+    opaqueColor = color.opacify(0.5)

     opaqueColor.a
-    # => 0.25
+    # => 0.75
+
+    # to see what they look like
+    for color, index in [color, opaqueColor]
+      canvas.drawRect
+        color: color
+        x: 20 + (60 * index)
+        y: 20 + (60 * index)
+        width: 60
+        height: 60

     @name opacify
@@ -1026,7 +1035,7 @@
     color = Color(0, 0, 0, 0)

     color.a
-    # => 1
+    # => 0

     # We modify color in place
     color.opacify$(0.25)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -128,7 +128,7 @@
       @copy().complement$()

     complement$: ->
-      @hue$(180)
+      @shiftHue$(180)

     copy: ->
       Color(@r, @g, @b, @a)
@@ -174,7 +174,7 @@
       return this

     shiftHue: (degrees) ->
-      @copy().hue$(degrees)
+      @copy().shiftHue$(degrees)

     shiftHue$: (degrees) ->
       hsl = @toHsl()


removed dead code

removed dead code

--- js-library/app/js/models/fact.coffee
+++ js-library/app/js/models/fact.coffee
@@ -10,9 +10,6 @@
     @_bound_events = {}

     @elements = elems
-
-    @obj =
-      id: id

     @createEventHandlers(@_events)


updated test to verify state after saving model.

--- tests/crudTests.coffee
+++ tests/crudTests.coffee
@@ -39,4 +39,8 @@
 console.log "beer is in #{beer.stateName}"

 console.log "me.toJSON: #{util.inspect me.toJSON()}"
-console.log "beer.toJSON is #{util.inspect beer.toJSON()}"
+console.log "beer.toJSON: #{util.inspect beer.toJSON()}"
+
+me.save()
+
+console.log me.stateName


Make sure to end range requests that end short of our file

--- src/podroller/core.coffee
+++ src/podroller/core.coffee
@@ -164,7 +164,6 @@
             requestStart    = rangeVals[1] - 0
             requestEnd      = rangeVals[2] - 0 or undefined

-
         @loadPreroll k.stream_key, req, (predata = null) =>
             # compute our final size
             # If this isn't a range request, then this info won't
@@ -186,6 +185,7 @@
             rangeEnd    = fend

             if rangeRequest
+                # FIXME: Instead of changing start like this, we should be returning a 416
                 rangeStart  = if (requestStart  <= fend) then requestStart else 0
                 rangeEnd    = if (requestEnd    <= fend) then requestEnd   else fend

@@ -261,6 +261,8 @@
                     # got to the end of the file.  close our response
                     console.debug "(stream end) wrote #{ res.socket?.bytesWritten } bytes. #{@listeners} active downloads."
                     res.end()
+            else
+                res.end()

             req.connection.on "end", =>
                 # connection aborted.  destroy our stream


Be slightly more guarded in testing what we should send

--- src/podroller/core.coffee
+++ src/podroller/core.coffee
@@ -235,10 +235,10 @@
             fileStart       = prerollEnd

             # write id3?
-            if k.id3 && rangeStart < k.id3.length
+            if k.id3? && rangeStart < k.id3.length
                 res.write k.id3.slice(rangeStart,rangeEnd)

-            if predata && ( rangeStart < prerollStart < rangeEnd ) || ( rangeStart < prerollEnd < rangeEnd )
+            if predata? && ( rangeStart < prerollStart < rangeEnd ) || ( rangeStart < prerollEnd < rangeEnd )
                 pstart = rangeStart - prerollStart
                 pstart = 0 if pstart < 0


only init pusher if available

only init pusher if available

--- components/layout/client.coffee
+++ components/layout/client.coffee
@@ -30,7 +30,7 @@
       prefillSuccess: -> mediator.trigger 'current_user:prefetched'
       success: -> mediator.trigger 'current_user:fetched'

-  mediator.shared.pusher = new Pusher sd.PUSHER_KEY
+  mediator.shared.pusher = new Pusher(sd.PUSHER_KEY) if Pusher?

 setupAjaxHeaders = ->
   $.ajaxSetup


Remove Slug Split in About Index

Remove Slug Split in About Index

--- app/pages/project/about/index.cjsx
+++ app/pages/project/about/index.cjsx
@@ -29,8 +29,7 @@
       .reduce(((accum, page) -> accum[page.url_key] = page.title; accum), {})

   render: ->
-    [ownerName, name] = @props.project.slug.split('/')
-    projectPath = "/projects/#{ownerName}/#{name}"
+    projectPath = "/projects/#{@props.project.slug}"

     <div className="project-about-page">
       <PromiseRenderer promise={@props.project.get 'pages'}>{(pages) =>


adding new getUriFromResultDataWithMaxSize function and refactoring getUriFromResultData

--- src/giphy.coffee
+++ src/giphy.coffee
@@ -146,9 +146,25 @@
     if data and callback and data.length > 0
       callback(if data.length == 1 then data[0] else data[Math.floor(Math.random() * data.length)])

+  getUriFromResultDataWithMaxSize: (images, size = 0, allowLargerThanMaxSize = false) ->
+    if images and images.length and size > 0
+      imagesBySize = Object.keys images
+        .sort (a, b) -> a.size - b.size
+
+      allowedImages = imagesBySize
+        .filter (x) -> x.size <= size
+
+      if allowedImages and allowedImages.length > 0
+        allowedImages[allowedImages.length - 1]
+      else if allowLargerThanMaxSize
+        imagesBySize[0]
+
   getUriFromResultData: (data) ->
-    if data and data.images and data.images.original
-      data.images.original.url
+    if data and data.images
+      if @maxSize > 0
+        @getUriFromResultDataWithMaxSize data.images, @maxSize, @allowLargerThanMaxSize
+      else if data.images.original
+        data.images.original.url

   getUriFromRandomResultData: (data) ->
     if data


Change markers on selection. Rise on hover.

--- app/scripts/controllers/map.coffee
+++ app/scripts/controllers/map.coffee
@@ -40,31 +40,41 @@
           marker = $window.L.marker latLng,
             title: item.properties.name
             alt: item.properties.name
+            riseOnHover: yes
             icon: $window.L.MakiMarkers.icon
-              'size': item.properties['marker-size']
+              'size': 's'
               'icon': item.properties['marker-symbol']
               'color': item.properties['marker-color']

           # Bind to clicks
           marker.on 'click', ->
-            # Reset currently selected marker
-            if $scope.selected
-              $scope.selected.marker.setIcon $window.L.MakiMarkers.icon
-                  'size': $scope.selected.properties['marker-symbol']
-                  'icon': $scope.selected.properties['marker-symbol']
-                  'color': $scope.selected.properties['marker-color']
-            # Marker size large
-            marker.setIcon $window.L.MakiMarkers.icon
-              'size': 'large'
-              'icon': @properties['marker-symbol']
-              'color': @properties['marker-color']
-            # Set as selected
             $scope.selected = @
           , item # To be executed with the context of item

           # Keep track
           item.marker = marker
+          item.zIndex = marker._zIndex
           $scope.items.push item
+
+    # Watch for selected changes
+    $scope.$watch 'selected', (newSelection, oldSelection) ->
+      # Reset currently selected marker
+      if oldSelection
+        oldSelection.marker.setIcon $window.L.MakiMarkers.icon
+            'size': 's'
+            'icon': oldSelection.properties['marker-symbol']
+            'color': oldSelection.properties['marker-color']
+        # Reset zIndex
+        oldSelection.marker.setZIndexOffset oldSelection.zIndex
+
+      return unless newSelection
+      # Marker size large
+      newSelection.marker.setIcon $window.L.MakiMarkers.icon
+        'size': 'l'
+        'icon': newSelection.properties['marker-symbol']
+        'color': newSelection.properties['marker-color']
+      # On top
+      newSelection.marker.setZIndexOffset 1000

     # Watch for marker changes
     $scope.$watchCollection 'items', (items) ->


Fix unbinding of destroyed

Fix unbinding of destroyed

--- app/scripts/lib/LazerScript.coffee
+++ app/scripts/lib/LazerScript.coffee
@@ -178,6 +178,11 @@
       delta += if settings.y? then Math.abs(settings.y - (enemy.y + Crafty.viewport.y)) else 0

       defer = WhenJS.defer()
+
+      onDestroy = =>
+        delete @enemies[settings.enemy ? 'default']
+        defer.resolve()
+
       enemy.choreography(
         [
           type: 'viewport'
@@ -188,12 +193,9 @@
         ]
       ).bind('ChoreographyEnd', ->
         @unbind('ChoreographyEnd')
-        @unbind('Destroyed')
+        @unbind('Destroyed', onDestroy)
         defer.resolve()
-      ).bind('Destroyed', =>
-        delete @enemies[settings.enemy ? 'default']
-        defer.resolve()
-      )
+      ).bind('Destroyed', onDestroy)
       defer.promise

   location: (settings = {}) ->


static loggedout home page markup change

--- workers/social/lib/social/render/kodinghomeloggedout.coffee
+++ workers/social/lib/social/render/kodinghomeloggedout.coffee
@@ -23,7 +23,7 @@

-
+

       #{getHomeIntro()}

@@ -31,17 +31,15 @@

-

-

-

-                    Learn more...
-                    Request an Invite
-                    Register an account
-                    Login
-

-                  #{getCounters()}
+

+

+                  Learn more...
+                  Request an Invite
+                  Register an account
+                  Login

-

+                #{getCounters()}
+

Featured Activity