pdate for card restrictions

pdate for card restrictions

Notice that there is nothing from wave 6 within the cards-es.coffee file. So I skipped that new changes from being translated.

--- coffeescripts/cards-es.coffee
+++ coffeescripts/cards-es.coffee
@@ -650,7 +650,7 @@
         "Chardaan Refit":
             name: "Reajuste Chardaan"
             ship: "Ala-A"
-            text: """Esta carta tiene un valor negativo en puntos de escuadrón."""
+            text: """text: """Solo Ala-A.

This card has a negative squad point cost."""Esta carta tiene un valor negativo en puntos de escuadrón."""
         "Proton Rockets":
             name: "Cohetes de Protones"
             text: """Ataque (Concentración): Descarta esta carta para realizar este ataque.

Puedes tirar dados de ataque adicionales igual a tu valor de agilidad, hasta un máximo de 3 dados de ataque adicionales."""
@@ -678,7 +678,7 @@
             text: """Cuando otra nave aliada que tengas a alcance 1 efectúe un ataque, podrá cambiar 1 de sus resultados de %HIT% por 1 resultado de %CRIT%."""
         "Expanded Cargo Hold":
             name: "Bodega de Carga Ampliada"
-            text: """Una vez por ronda, cuando tengas a recibir una carta de Daño boca arriba, puedes robar esa carta del mazo de Daño de proa o del mazo de Daño de popa."""
+            text: """Solo GR-75

Una vez por ronda, cuando tengas a recibir una carta de Daño boca arriba, puedes robar esa carta del mazo de Daño de proa o del mazo de Daño de popa."""
             ship: 'Transporte mediano GR-75'
         "Backup Shield Generator":
             name: "Generador de Escudos Auxiliar"
@@ -764,17 +764,17 @@
             text: """Cuando un ataque con armamento secundario que requiera descartarlo para efectuar el ataque, no se descarta a menos que el ataque impacte al objetivo."""
         "Stygium Particle Accelerator":
             name: "Acelerador de Partículas de Stygium"
-            text: """Cuando realices una acción de Encubrimiento, o te Descubras, puedes realizar una acción de Evadir gratuita."""
+            text: """Soo TIE Fantasma.

Cuando realices una acción de Encubrimiento, o te Descubras, puedes realizar una acción de Evadir gratuita."""
         "Advanced Cloaking Device":
             name: "Dispositivo de Camuflaje Avanzado"
             text: """Despues de que efectúes un ataque, puedes realizar una acción gratuita de camuflaje."""
             ship: "TIE Fantasma"
         "Combat Retrofit":
             name: "Equipamiento de Combate"
-            text: """Tu valor de casco se incrementa en 2 y tu valor de escudos se incrementa en 1."""
+            text: """Solo GR-75.

Tu valor de casco se incrementa en 2 y tu valor de escudos se incrementa en 1."""
             ship: 'Transporte mediano GR-75'
         "B-Wing/E2":
-            text: """Tu barra de mejoras gana %CREW%."""
+            text: """Solo ala-B.

Tu barra de mejoras gana %CREW%."""
             ship: "Ala-B"
         "Countermeasures":
             text: """Al comienzo de la fase de combate, puedes descartar esta carta para incrementar tu valor de agilidad en 1 hasta el final de la fase.  Retira un marcador de blanco fijado de tu nave."""
@@ -786,51 +786,51 @@
     title_translations =
         "Slave I":
             name: "Esclavo 1"
-            text: """Tu barra de mejoras gana el ícono %TORPEDO%."""
+            text: """Solo Firespray-31.

Tu barra de mejoras gana el ícono %TORPEDO%."""
         "Millennium Falcon":
             name: "Halcón Milenario"
-            text: """Tu barra de acciones gana el ícono %EVADE%."""
+            text: """Solo YT-1300.

Tu barra de acciones gana el ícono %EVADE%."""
         "Moldy Crow":
             name: "Cuervo Oxidado"
-            text: """Durante la fase final, no retires las fichas de Concentración que no hayas usado."""
+            text: """Solo HWK-290.

Durante la fase final, no retires las fichas de Concentración que no hayas usado."""
         "ST-321":
-            text: """Cuando fijes un blanco, puedes hacerlo con cualquier nave enemiga de la zona de juego."""
+            text: """Solo Lambda-class Shuttle.

Cuando fijes un blanco, puedes hacerlo con cualquier nave enemiga de la zona de juego."""
             ship: "Lanzadera clase Lambda"
         "Royal Guard TIE":
             name: "TIE de la Guardia Real"
             ship: "Interceptor TIE"
-            text: """Puedes equipar a tu nave con 2 mejoras de Modificación diferentes (en vez de una).

No puedes tener este título si tu habilidad de piloto es "4" o menos."""
+            text: """Solo TIE Interceptor.

Puedes equipar a tu nave con 2 mejoras de Modificación diferentes (en vez de una).

No puedes tener este título si tu habilidad de piloto es "4" o menos."""
         "Dodonna's Pride":
             name: "Orgullo de Donna"
-            text: """Cuando efectúes una accion de Coordinación, puedes elegir 2 naves en vez de 1. Cada una de estas naves pueden realizar 1 accion gratuita."""
+            text: """Solo sección delantera de CR90.

Cuando efectúes una accion de Coordinación, puedes elegir 2 naves en vez de 1. Cada una de estas naves pueden realizar 1 accion gratuita."""
             ship: "Corbeta CR90 (Proa)"
         "A-Wing Test Pilot":
             name: "Piloto de Pruebas de Ala-A"
-            text: """Tu barra de mejoras gana el ícono %ELITE%.

No puedes equipar dos %ELITE% iguales. No puedes tener este título si tu habilidad de piloto es "1" o menor."""
+            text: """Solo ala-A.

Tu barra de mejoras gana el ícono %ELITE%.

No puedes equipar dos %ELITE% iguales. No puedes tener este título si tu habilidad de piloto es "1" o menor."""
             ship: "Ala-A"
         "Tantive IV":
             name: "Tantive IV"
-            text: """La barra de mejoras de tu sección de proa gana 1 icono adicional de %CREW% y 1 icono adicional de %TEAM%."""
+            text: """Solo sección delantera de CR90.

La barra de mejoras de tu sección de proa gana 1 icono adicional de %CREW% y 1 icono adicional de %TEAM%."""
             ship: "Corbeta CR90 (Proa)"
         "Bright Hope":
             name: "Esperanza Brillante"
-            text: """Una ficha de Refuerzo asignada a tu seccion de proa añade 2 resultados de %EVADE% en vez de 1."""
+            text: """Solo GR-75.

Una ficha de Refuerzo asignada a tu seccion de proa añade 2 resultados de %EVADE% en vez de 1."""
             ship: 'Transporte mediano GR-75'
         "Quantum Storm":
             name: "Tormenta Cuántica"
-            text: """Al principio de la fase Final, si tienes 1 ficha de Energía o menos, ganas 1 ficha de Energía."""
+            text: """SOlo GR-75.

Una acción de refuerzo asignada a la sección delantera añade 2 resultados de %EVADE% (en lugar de 1).""Al principio de la fase Final, si tienes 1 ficha de Energía o menos, ganas 1 ficha de Energía."""
             ship: 'Transporte mediano GR-75'
         "Dutyfree":
             name: "Libre de Impuestos"
-            text: """Cuando realices una acción de Interferencia, puedes elegir una nave enemiga situada a alcance 1-3 en lugar de 1-2."""
+            text: """Solo GR-75./span>

Una acción de refuerzo asignada a la sección delanteraañade 2 resultados de %EVADE% (en lugar de 1).""Cuando realices una acción de Interferencia, puedes elegir una nave enemiga situada a alcance 1-3 en lugar de 1-2."""
             ship: 'Transporte mediano GR-75'
         "Jaina's Light":
             name: "Luz de Jaina"
-            text: """Cuando te defiendas, una vez por ataque. Si recibes una carta de Daño boca arriba, puedes descartarla y robar otra carta de Daño boca arriba."""
+            text: """Solo sección delantera de CR90.

Cuando te defiendas, una vez por ataque. Si recibes una carta de Daño boca arriba, puedes descartarla y robar otra carta de Daño boca arriba."""
         "Outrider":
             name: "Jinete del Espacio"
-            text: """Mientras tu nave tenga una mejora de %CANNON% equipada, no podrá realizar atques con el armamento principal attacks pero podrá realizar ataques con su mejora de armamento secundario %CANNON% contra naves enemigas fuera de su arco de fuego principal."""
+            text: """Solo YT-2400.

Mientras tu nave tenga una mejora de %CANNON% equipada, no podrá realizar atques con el armamento principal attacks pero podrá realizar ataques con su mejora de armamento secundario %CANNON% contra naves enemigas fuera de su arco de fuego principal."""
         "Dauntless":
-            text: """Después de ejecutar una maniobra que te haga solapar con otra nave, puedes realizar 1 acción gratuita. Recibe 1 ficha de tensión."""
+            text: """Solo Decimator VT-49.

Después de ejecutar una maniobra que te haga solapar con otra nave, puedes realizar 1 acción gratuita. Recibe 1 ficha de tensión."""

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


cmd-n

cmd-n

--- src/atom/key-bindings.coffee
+++ src/atom/key-bindings.coffee
@@ -1,5 +1,6 @@
 app:
   'cmd-q': (app) -> app.quit()
+  'cmd-n': (app) -> atom.native.newWindow()

 window:
   'cmd-shift-I': (window) -> window.showConsole()


Adds isEigen check and clean up substring matching

--- components/util/device.coffee
+++ components/util/device.coffee
@@ -1,7 +1,11 @@
 module.exports =
   isIPad: ->
-    userAgent = navigator.userAgent.toLowerCase()
-    userAgent.search('ipad') > -1
+    navigator.userAgent.toLowerCase()
+      .indexOf('ipad') > -1
+
+  isEigen: ->
+    navigator.userAgent.toLowerCase()
+      .indexOf('Artsy-Mobile') > -1

   # http://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript
   isTouchDevice: ->
@@ -17,4 +21,5 @@
     (window.devicePixelRatio or 1) > 1

   isFirefox: ->
-    navigator.userAgent.toLowerCase().indexOf('firefox') > -1
+    navigator.userAgent.toLowerCase()
+      .indexOf('firefox') > -1


Fixed quotes

Fixed quotes

--- source/browser.coffee
+++ source/browser.coffee
@@ -113,7 +113,7 @@
     return editor

   if code = window.location.href.match(/\?code=(.*)/)?[1]
-    $.getJSON 'https://hamljr-auth.herokuapp.com/authenticate/#{code}', (data) ->
+    $.getJSON "https://hamljr-auth.herokuapp.com/authenticate/#{code}", (data) ->
       if token = data.token
         Gistquire.authToken = token
         localStorage.authToken = token


Clean up alert options handling

Clean up alert options handling

--- src/bootstrap-dialogs.coffee
+++ src/bootstrap-dialogs.coffee
@@ -30,16 +30,20 @@
 exports = Bootstrap.Dialogs =

   alert: (options={}) ->
-    title = options.title or 'Alert'
-    body = options.body
-    okText = options.ok or 'Ok'
+    defaultOptions =
+      title: 'Alert'
+      ok: 'Ok'
+      danger: false
+    options = $.extend(defaultOptions, options)
+
     okClass = if options.danger then 'danger' else 'primary'
     promise = exports.dialog
-      title: title
-      body: body
+      title: options.title
+      body: options.body
       buttons: [
-        [ mkbutton(okText, okClass), -> promise.resolve() ]
+        [ mkbutton(options.ok, okClass), -> promise.resolve() ]
       ]
+
     returnHandler = (e) -> promise.resolve() if e.which is RETURN
     $('body').on('keyup', returnHandler)
     promise.always ->


add spec

add spec

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -304,6 +304,12 @@
         stream = repeat(20, ["1", "2"]).delay(10).take(4)
         prop.sampledBy(stream, add)
       ["a1", "b2", "b1", "b2"])
+  it "works with same origin", ->
+    expectStreamEvents(
+      ->
+        src = repeat(20, [1, 2]).take(2)
+        src.toProperty().sampledBy(src.map(times(2)))
+      [1, 2])

 describe "Property.sample", ->
   it "samples property by given interval", ->


Reverse route can handle optional parameters

--- src/chaplin/lib/route.coffee
+++ src/chaplin/lib/route.coffee
@@ -123,7 +123,8 @@

       # Convert params from array into object.
       paramsHash = {}
-      for paramName, paramIndex in @requiredParams
+      routeParams = @requiredParams.concat @optionalParams
+      for paramName, paramIndex in routeParams
         paramsHash[paramName] = params[paramIndex]

       return false unless @testConstraints paramsHash


Added error checking for both forms of reversal.

--- src/chaplin/lib/route.coffee
+++ src/chaplin/lib/route.coffee
@@ -42,14 +42,24 @@
       # the actual URL this route represents
       # Iterate and attempt to replace params in pattern
       if _.isArray params
+        # Ensure we have enough parameters
+        if params.length < @paramNames.length
+          throw new Error 'Route: Not enough parameters to reverse'
+
         index = 0
         url = url.replace /[:*][^\/\?]+/g, (match) ->
           result = params[index]
           index += 1
           result
       else
-        for name, value of params
+        for name in @paramNames
+          value = params[name]
+
+          if value is undefined
+            throw new Error 'Route: Not enough parameters to reverse'
+
           url = url.replace ///[:*]#{name}///g, value
+
       # If the url tests out good; return the url; else, false
       if @test url then url else false


show events in order always

show events in order always

--- src/coffee/controllers/player/player-overview-ctrl.coffee
+++ src/coffee/controllers/player/player-overview-ctrl.coffee
@@ -122,7 +122,7 @@
       $scope.player = Player.getPlayer()
       $scope.loadPersonalities()

-      $scope._recentEvents = $scope.player?.recentEvents.reverse()
+      $scope._recentEvents = $scope.player?.recentEvents
       $scope._personalities = _($scope.player?.achievements)
         .filter (achievement) -> achievement.type is 'personality'
         .pluck '_personality'


Temp make sendEmailValidation param optional

--- src/collections/users.coffee
+++ src/collections/users.coffee
@@ -24,10 +24,9 @@
   @saveItem user, callback

-exports.sendEmailValidation = (params, callback) ->
-  unless params?
-    throw new TSArgsError 'teamsnap.sendEmailValidation', 'must provide an object with params'
-  unless params.teamId? and @isId params.teamId
-    throw new TSArgsError 'teamsnap.sendEmailValidation', 'must provide a teamId'
-
-  @collections.users.exec('sendEmailValidation', params).pop().callback callback+exports.sendEmailValidation = (params = {}, callback) ->
+  if Object.keys(params).length and !(params.teamId? and @isId params.teamId)
+    throw new TSArgsError 'teamsnap.sendEmailValidation', 'if a params object is
+      used, it must contain a teamId'
+
+  @collections.users.exec('sendEmailValidation', params).pop().callback callback


Remove unused template handler.

Remove unused template handler.

--- client/controllers/article.coffee
+++ client/controllers/article.coffee
@@ -1,11 +0,0 @@
-Template.article.events
-  "click .delete-article": (e) ->
-    incidentCount = Incidents.find({userEventId: @userEventId, url: @url}).count()
-    if incidentCount
-      plural = if incidentCount is 1 then "" else "s"
-      message = "There " +
-        (if incidentCount is 1 then "is an incident report" else "are #{incidentCount} incident reports") +
-        " associated with this article. Please delete the incident report#{plural} before deleting the article."
-      toastr.error(message)
-    else if window.confirm("Are you sure you want to delete this article?")
-      Meteor.call("removeEventArticle", @_id)


Update web API stub.

Update web API stub.

--- services/document-updater/test/acceptance/coffee/FlushingDocsTests.coffee
+++ services/document-updater/test/acceptance/coffee/FlushingDocsTests.coffee
@@ -63,7 +63,7 @@
 				lines: @lines
 				version: @version
 			}
-			sinon.stub MockWebApi, "setDocument", (project_id, doc_id, lines, version, callback = (error) ->) ->
+			sinon.stub MockWebApi, "setDocument", (project_id, doc_id, lines, version, ranges, callback = (error) ->) ->
 				setTimeout callback, 30000
 			DocUpdaterClient.preloadDoc @project_id, @doc_id, done


Spy on shell instead.

Spy on shell instead.

--- spec/about-spec.coffee
+++ spec/about-spec.coffee
@@ -1,3 +1,4 @@
+shell = require 'shell'
 About = require '../lib/about'
 {$} = require 'atom-space-pen-views'

@@ -79,6 +80,6 @@
         triggerUpdate()
         expect(workspaceElement).toContain('.about-release-notes')

-        dispatchCall = spyOn(atom.commands, 'dispatch')
+        releaseNotesCall = spyOn(shell, 'openExternal')
         $(workspaceElement).find('.about-release-notes').trigger('click')
-        expect(dispatchCall.mostRecentCall.args[1]).toBe 'about:view-release-notes'
+        expect(releaseNotesCall.mostRecentCall.args[0]).toBe 'https://atom.io/releases'


:white_check_mark: Fix folding test

:white_check_mark: Fix folding test

- There doesn't seem to be any real functionality broken.  This way of
  explicitly calling the row to fold for the test is as good.

--- spec/jumpy-spec.coffee
+++ spec/jumpy-spec.coffee
@@ -181,7 +181,7 @@
                 classList.contains('jumpy-jump-mode')).not.toBeTruthy()
         it "jumps the cursor in folded regions", ->
             textEditor.setCursorBufferPosition [23, 20]
-            textEditor.foldCurrentRow()
+            textEditor.foldBufferRow(22)
             atom.commands.dispatch workspaceElement, 'jumpy:toggle'
             atom.commands.dispatch workspaceElement, 'jumpy:d'
             atom.commands.dispatch workspaceElement, 'jumpy:i'


gamestate

gamestate

--- src/engine_game_state.coffee
+++ src/engine_game_state.coffee
@@ -1,16 +1,29 @@
 # TODO: This is a work in progress
 Engine.GameState = (I, self) ->
-  # Default Game State
-  currentState = GameState()
+  Object.reverseMerge I,
+    currentState: GameState()
+
+  requestedState = null

   # The idea is that all the engine#beforeUpdate triggers happen
   # before the state beforeUpdate triggers, then the state update
   # then the state after update, then the engine after update
   # like a layered cake with states in the middle.
   self.bind "update", ->
-    currentState.trigger "beforeUpdate"
-    currentState.trigger "update"
-    currentState.trigger "afterUpdate"
+    I.currentState.trigger "beforeUpdate"
+    I.currentState.trigger "update"
+    I.currentState.trigger "afterUpdate"
+
+  self.bind "afterUpdate", ->
+    if requestedState?
+      I.currentState.trigger "exit", requestedState
+
+      previousState = I.currentState
+      I.currentState = requestedState
+
+      I.currentState.trigger "enter", previousState
+
+      requestedState = null

   # TODO: Drawing, cameras??

@@ -18,8 +31,10 @@
   return {
     # Just passs through to the current state
     add: (entityData) ->
-      currentState.add(entityData)
+      I.currentState.add(entityData)
     objects: ->
-      currentState.objects()
+      I.currentState.objects()
+    setState: (newState) ->
+      requestedState = newState
   }


Use `or` instead of `?`.

Use `or` instead of `?`.

--- src/fs_utils/generate.coffee
+++ src/fs_utils/generate.coffee
@@ -20,12 +20,12 @@
 sortByConfig = (files, config) ->
   if toString.call(config) is '[object Object]'
     criteria = [
-      config.before ? []
-      config.after ? []
-      config.joinToValue ? []
-      config.bower ? []
-      config.component ? []
-      config.vendorConvention ? -> no
+      config.before or []
+      config.after or []
+      config.joinToValue or []
+      config.bower or []
+      config.component or []
+      config.vendorConvention or (-> no)
     ]
     anysort.grouped files, criteria, [0, 2, 3, 4, 5, 6, 1]
   else


:lipstick:

:lipstick:

--- src/git.coffee
+++ src/git.coffee
@@ -19,10 +19,10 @@

 newStatusFlags = statusWorkingDirNew | statusIndexNew

-Repository.prototype.getWorkingDirectory = ->
+Repository::getWorkingDirectory = ->
   @getPath()?.replace(/\/\.git\/?$/, '')

-Repository.prototype.getShortHead = ->
+Repository::getShortHead = ->
   head = @getHead()
   return head unless head?
   return head.substring(11) if head.indexOf('refs/heads/') is 0
@@ -31,19 +31,19 @@
   return head.substring(0, 7) if head.match(/[a-fA-F0-9]{40}/)
   return head

-Repository.prototype.isStatusModified = (status=0) ->
+Repository::isStatusModified = (status=0) ->
   (status & modifiedStatusFlags) > 0

-Repository.prototype.isPathModified = (path) ->
+Repository::isPathModified = (path) ->
   @isStatusModified(@getStatus(path))

-Repository.prototype.isStatusNew = (status=0) ->
+Repository::isStatusNew = (status=0) ->
   (status & newStatusFlags) > 0

-Repository.prototype.isPathNew = (path) ->
+Repository::isPathNew = (path) ->
   @isStatusNew(@getStatus(path))

-Repository.prototype.getUpstreamBranch = (branch) ->
+Repository::getUpstreamBranch = (branch) ->
   branch ?= @getHead()
   return null unless branch?.length > 11
   return null unless branch.indexOf('refs/heads/') is 0
@@ -59,7 +59,7 @@

   "refs/remotes/#{branchRemote}/#{branchMerge.substring(11)}"

-Repository.prototype.getAheadBehindCount = ->
+Repository::getAheadBehindCount = ->
   counts =
     ahead: 0
     behind: 0
@@ -78,7 +78,7 @@
   counts.behind = @getCommitCount(upstreamCommit, mergeBase)
   counts

-Repository.prototype.relativize = (path) ->
+Repository::relativize = (path) ->
   return path unless path?
   return path unless path[0] is '/'


Add status query for staged files

Add status query for staged files

This allows clients to determine whether a path is in git's index.

--- src/git.coffee
+++ src/git.coffee
@@ -21,6 +21,10 @@

 deletedStatusFlags = statusWorkingDirDelete | statusIndexDeleted

+indexStatusFlags = statusIndexNew | statusIndexModified |
+                   statusIndexDeleted | statusIndexRenamed |
+                   statusIndexTypeChange
+
 Repository::release = ->
   submoduleRepo?.release() for submodulePath, submoduleRepo of @submodules
   @_release()
@@ -57,6 +61,9 @@

 Repository::isStatusIgnored = (status=0) ->
   (status & statusIgnored) > 0
+
+Repository::isStatusStaged = (status=0) ->
+  (status & indexStatusFlags) > 0

 Repository::getUpstreamBranch = (branch) ->
   branch ?= @getHead()


Remove duplicate unbind code, this is already done in view

--- src/chaplin/views/collection_view.coffee
+++ src/chaplin/views/collection_view.coffee
@@ -395,9 +395,6 @@
     dispose: ->
       return if @disposed

-      # Remove all listeners registered in this context
-      @collection.off null, null, this
-
       # Dispose all item views
       view.dispose() for own cid, view of @viewsByCid


Remeasure height of headroom when children change fixes #2

--- src/index.cjsx
+++ src/index.cjsx
@@ -41,7 +41,7 @@
     className: 'headroom headroom--pinned'

   componentDidMount: ->
-    @setState height: @getDOMNode().offsetHeight
+    @setState height: @refs.inner.getDOMNode().offsetHeight
     unless @props.disable
       window.addEventListener('scroll', @handleScroll)

@@ -55,6 +55,10 @@
     else if not nextProps.disable and @props.disable
       window.addEventListener('scroll', @handleScroll)

+  componentDidUpdate: (prevProps, prevState) ->
+    # If children have changed, remeasure height.
+    if prevProps.children isnt @props.children
+      @setState height: @refs.inner.getDOMNode().offsetHeight

   handleScroll: ->
     unless @ticking
@@ -146,7 +150,7 @@
       style = @props.style

-

+

         {@props.children}


Add unselected state for relationship magnitude

This forces the user to select an option.

This required adding a view state to hold on to the currently selected
relationship vector, since we can't create a relationship until we
have both the vector and the scalar.

[#117182021]

--- src/code/views/link-relation-view.coffee
+++ src/code/views/link-relation-view.coffee
@@ -39,13 +39,25 @@
       sourceNode:
         title: "default source node"

+  getInitialState: ->
+    selectedVector: null
+
+  componentWillReceiveProps: (newProps) ->
+    if @props.link isnt newProps.link
+      @setState
+        selectedVector: null
+
   updateRelation: ->
-    vector   = @getVector()
-    scalar   = @getScalar()
+    selectedVector = @getVector()
+    selectedScalar = @getScalar()

-    relation = RelationFactory.fromSelections(vector, scalar)
-    link = @props.link
-    @props.graphStore.changeLink(link, {relation: relation})
+    @setState {selectedVector}
+
+
+    if selectedVector? and selectedScalar?
+      relation = RelationFactory.fromSelections(selectedVector, selectedScalar)
+      link = @props.link
+      @props.graphStore.changeLink(link, {relation: relation})

   getVector: ->
     id = parseInt @refs.vector.value
@@ -75,10 +87,19 @@
   renderScalarPulldown:(amount) ->
     options = _.map RelationFactory.scalars, (opt, i) ->
       (option {value: opt.id, key: i}, opt.text)
-    currentOption = amount?.id ? 0
+
+    if not amount?
+      options.unshift (option {key: "placeholder", value: "unselected", disabled: "disabled"},
+        tr "~NODE-RELATION-EDIT.UNSELECTED")
+      currentOption = "unselected"
+    else
+      currentOption = amount.id
+
+    disabled = "disabled" unless @state.selectedVector or amount?
+
     (div {className: "bb-select"},
       (span {}, "#{tr "~NODE-RELATION-EDIT.BY"} ")
-      (select {value: currentOption, className:"", ref: "scalar", onChange: @updateRelation, disabled: if amount then false else "disabled"},
+      (select {value: currentOption, className:"", ref: "scalar", onChange: @updateRelation, disabled: disabled},
         options
       )
     )
@@ -87,6 +108,7 @@
     source = @props.link.sourceNode.title
     target = @props.link.targetNode.title
     {vector, scalar} = RelationFactory.selectionsFromRelation @props.link.relation
+    vector ?= @state.selectedVector
     formula = @props.link.relation.formula
     (div {className: 'link-relation-view'},
       (div {className: 'top'},


fixed bug in unwrapping the last element

--- src/coffee/responsive.breadcrumbs.coffee
+++ src/coffee/responsive.breadcrumbs.coffee
@@ -90,9 +90,10 @@

             # Initialize
             o.el            = $ el
-            o.cloneEl       = o.el.clone() # Reference to the old untouched element
+            o.cloneEl       = o.el.clone()            # Reference to the old untouched element
             o.browserWindow = $ window
             o.windowWidth   = o.browserWindow.width()
+            o.unwrapWidth   =  o.getChildrenWidth()   # unwrapped width for the breadcrumbs

             o.opts.onLoad(_this) if $.isFunction(o.opts.onLoad)

@@ -212,8 +213,11 @@
                         # query again the dom and store it again in a variable
                         hiddenItems = holder.find 'ul > li'

+                        # remove the remaining hidden item if the parent width and greater than or equal to the unwrap width
+                        holder.after hiddenItems.detach() if hiddenItems.length is 1 and o.el.width() >= o.unwrapWidth
+
                         # remove the holder if there is no more item left.
-                        if hiddenItems.length is 0
+                        if hiddenItems.length <= 1 and o.el.width() >= o.unwrapWidth
                             holder.remove()

                             # delete the reference since the holder element have been remove already


Return empty string unless configDirPath is passed

--- src/keymap-extensions.coffee
+++ src/keymap-extensions.coffee
@@ -20,6 +20,8 @@
   @emitter.emit 'did-load-bundled-keymaps'

 KeymapManager::getUserKeymapPath = ->
+  return "" unless @configDirPath?
+
   if userKeymapPath = CSON.resolve(path.join(@configDirPath, 'keymap'))
     userKeymapPath
   else


Use Date.now isntead of creating a new Date object

--- src/kissmetrics-batch.coffee
+++ src/kissmetrics-batch.coffee
@@ -139,7 +139,7 @@

   _transformData: (data) ->
     data.identity = data._p
-    data.timestamp = data._t || Math.round((new Date).getTime() / 1000)
+    data.timestamp = data._t || Math.round(Date.now() / 1000)

     switch data.__type
       when 'record' then data.event = data._n


Actually handle initialize() correctly.

Actually handle initialize() correctly.

--- src/jvm.coffee
+++ src/jvm.coffee
@@ -43,11 +43,14 @@
 # main function that gets called from the frontend
 root.run_class = (rs, class_name, cmdline_args, done_cb) ->
   main_spec = class: class_name, sig: 'main([Ljava/lang/String;)V'
-  yielded = false
-  run = (fn) ->
+  run = (fns...) ->
     try
-      fn()
-      true
+      fns[0]()
+      fns.shift()
+      if fns.length > 0
+        run fns...
+      else
+        done_cb?()
     catch e
       if e instanceof util.JavaException
         console.error "\nUncaught Java Exception"
@@ -56,16 +59,14 @@
       else if e instanceof util.HaltException
         console.error "\nExited with code #{e.exit_code}" unless e.exit_code is 0
       else if e instanceof util.YieldException
-        yielded = true
         e.condition ->
           rs.resuming_stack = 0  # <-- index into the meta_stack of the frame we're resuming
-          run()
+          run fns...
       else
         console.error "\nInternal JVM Error!"
         show_state(rs)
-        console.log e.stack
-      false
-  # we're assuming that initialize() never yields
-  (run -> rs.initialize(class_name,cmdline_args)) and
-  (run -> rs.method_lookup(main_spec).run(rs))
-  done_cb?() unless yielded
+        console.error e.stack
+      unless e instanceof util.YieldException
+        done_cb?()
+  run (-> rs.initialize(class_name,cmdline_args)),
+      (-> rs.method_lookup(main_spec).run(rs))


Extract method has_method

Extract method has_method

--- src/TinyMockJS.coffee
+++ src/TinyMockJS.coffee
@@ -47,7 +47,7 @@
   throw_expects_usage() unless method_name?
   throw_reserved_word(method_name) if is_reserved_word(method_name)
   throw_pre_existing_property(method_name) if is_pre_existing_property(object, method_name)
-  if is_class(object) and not object.prototype[ method_name ]?
+  if is_class(object) and not has_method(object, method_name)
     throw_not_an_existing_method(method_name)

 is_reserved_word = (word) ->
@@ -58,6 +58,9 @@

 is_class = (object) ->
    typeof object == 'function'
+
+has_method = (object, method_name) ->
+   object.prototype[ method_name ]?

 start_new_expectation = (object, method_name) ->
   new Expectation(object, method_name)


Fix empty string not rendering in ListView.

--- src/linkview/linkview.coffee
+++ src/linkview/linkview.coffee
@@ -132,7 +132,7 @@
                 itemLI.setAttribute attribute, item.getAttribute(attribute)
             itemLI.setAttribute 'depth', item.depth
             itemLI.innerHTML = item.bodyHighlightedAttributedString
-                                   .toInlineBMLString()
+                                   .toInlineBMLString() or ' '

             items.push(linkifyHtml itemLI.outerHTML, @linkifyOptions)


Relaunch Atom when changing the title bar style

Now that we have the required API

Signed-off-by: Max Brunsfeld <78036c9b69b887700d5846f26a788d53b201ffbb@github.com>

--- src/main-process/atom-application.coffee
+++ src/main-process/atom-application.coffee
@@ -734,10 +734,9 @@
   promptForRelaunch: ->
     chosen = dialog.showMessageBox BrowserWindow.getFocusedWindow(),
       type: 'warning'
-      title: 'Relaunch required'
-      message: "You will need to relaunch Atom for this change to take effect."
-      buttons: ['Quit Atom', 'Cancel']
+      title: 'Restart required'
+      message: "You will need to restart Atom for this change to take effect."
+      buttons: ['Restart Atom', 'Cancel']
     if chosen is 0
-      # once we're using electron v.1.2.2
-      # app.relaunch()
+      app.relaunch({args: []})
       app.quit()


HIRE-17: added select on focus

HIRE-17: added select on focus

--- src/main/java/app/utils/autofocus.coffee
+++ src/main/java/app/utils/autofocus.coffee
@@ -19,6 +19,9 @@
         restrict: 'A'
         link: ($scope, $element) ->
           $timeout ->
+            $element[0].focus = ->
+              this.select();
+              return;
             $element[0].focus()
             return
           return


data and find return renderable promises

--- src/ops.coffee
+++ src/ops.coffee
@@ -227,21 +227,23 @@

   fn 'data', 'Fetches Graphite graph data', (args...) ->
     params = args_to_params args, @
-    @async ->
+    @value @async ->
       $result = @output()
       promise = graphite.get_data params
-      promise.done (response) =>
-        for series in response
-          $header = $ '
'
-          $header.text series.target
-          $result.append $header
-          $table = $ '
'
-          for [value, timestamp] in series.datapoints
-            time = moment(timestamp * 1000)
-            $table.append "
#{time.format('MMMM Do YYYY, h:mm:ss a')}	#{value?.toFixed(3) or '(none)'}
"
-          $result.append $table
-      promise.fail (error) =>
-        @cli.error error
+      promise._lead_render = =>
+        promise.done (response) =>
+          for series in response
+            $header = $ '
'
+            $header.text series.target
+            $result.append $header
+            $table = $ '
'
+            for [value, timestamp] in series.datapoints
+              time = moment(timestamp * 1000)
+              $table.append "
#{time.format('MMMM Do YYYY, h:mm:ss a')}	#{value?.toFixed(3) or '(none)'}
"
+            $result.append $table
+        promise.fail (error) =>
+          @cli.error error
+      promise

   fn 'graph', 'Graphs a Graphite target using d3', (args...) ->
     params = args_to_params args, @
@@ -256,31 +258,35 @@

   fn 'find', 'Finds named Graphite metrics using a wildcard query', (query) ->
     query_parts = query.split '.'
-    @async ->
+    @value @async ->
       $result = @output()
-      promise = graphite.complete query
-      promise.done (response) =>
-        $ul = $ '
'
-        for node in response.metrics
-          $li = $ '
'
-          text = node.path
-          text += '*' if node.is_leaf == '0'
-          node_parts = text.split '.'
-          for part, i in node_parts
-            if i > 0
-              $li.append '.'
-            $span = $ ''
-            $span.addClass 'light' if part == query_parts[i]
-            $span.text part
-            $li.append $span
-          do (text) =>
-            $li.on 'click', =>
-              if node.is_leaf == '0'
-                @run "find #{JSON.stringify text}"
-              else
-                @run "q(#{JSON.stringify text})"
-          $ul.append $li
-        $result.append $ul
+      promise = graphite.complete(query).then (response) ->
+        response.metrics
+
+      promise._lead_render = =>
+        promise.done (metrics) =>
+          $ul = $ '
'
+          for node in metrics
+            $li = $ '
'
+            text = node.path
+            text += '*' if node.is_leaf == '0'
+            node_parts = text.split '.'
+            for part, i in node_parts
+              if i > 0
+                $li.append '.'
+              $span = $ ''
+              $span.addClass 'light' if part == query_parts[i]
+              $span.text part
+              $li.append $span
+            do (text) =>
+              $li.on 'click', =>
+                if node.is_leaf == '0'
+                  @run "find #{JSON.stringify text}"
+                else
+                  @run "q(#{JSON.stringify text})"
+            $ul.append $li
+          $result.append $ul
+      promise

   cmd 'permalink', 'Create a link to the code in the input cell above', (code) ->
     a = document.createElement 'a'


Fix mask calculation for pad adjacent to mounting hole

--- src/pattern/default/common/copper.coffee
+++ src/pattern/default/common/copper.coffee
@@ -84,13 +84,14 @@
           for j in [(i+1)..last] by 1
             p1 = pads[keys[i]]
             p2 = pads[keys[j]]
-            hspace = Math.abs(p2.x - p1.x) - (p1.width + p2.width)/2
-            vspace = Math.abs(p2.y - p1.y) - (p1.height + p2.height)/2
-            space = Math.max hspace, vspace
             mask = settings.clearance.padToMask
-            if (space - 2*mask) < settings.minimum.maskWidth
-              mask = (space - settings.minimum.maskWidth) / 2
-              if mask < 0 then mask = 0
+            if (p1.type isnt 'mounting-hole') and (p2.type isnt 'mounting-hole')
+              hspace = Math.abs(p2.x - p1.x) - (p1.width + p2.width)/2
+              vspace = Math.abs(p2.y - p1.y) - (p1.height + p2.height)/2
+              space = Math.max hspace, vspace
+              if (space - 2*mask) < settings.minimum.maskWidth
+                mask = (space - settings.minimum.maskWidth) / 2
+                if mask < 0 then mask = 0
             if (not p1.mask?) or (mask < p1.mask) then p1.mask = mask
             if (not p2.mask?) or (mask < p2.mask) then p2.mask = mask


fix an obvious async mistake discovered via traceback log

--- src/smc-hub/kucalc/compute-client.coffee
+++ src/smc-hub/kucalc/compute-client.coffee
@@ -502,7 +502,7 @@
                                     if x.size <= opts.maxsize
                                         cb()
                                         return
-                        cb('file too big or not found in listing')
+                            cb('file too big or not found in listing')
             (cb) =>
                 url = "http://project-#{@project_id}:6001/#{@project_id}/raw/#{opts.path}"
                 dbg("fetching file from '#{url}'")


fixed error in tests

fixed error in tests

--- platform/src/tests/tests.coffee
+++ platform/src/tests/tests.coffee
@@ -427,7 +427,7 @@
           paramDefs:
             service: ""
           update: ->
-            expect(tools.testTool(@params.service.a, 2)._1).toBe(1)
+            expect(@tools.testTool(@params.service.a, 2)._1).toBe(1)
             @params.service.a++

       layout =


Added drawFinished function for HTable, which called after every data refresh

--- js/tables/table/table.coffee
+++ js/tables/table/table.coffee
@@ -381,6 +381,8 @@
           @pushTask => @_drawCellStyles()
         if @drawTableExtras?
           @pushTask => @drawTableExtras()
+      if @drawFinished?
+        @pushTask => @drawFinished()
     if _viewDefs.length > 0
       @pushTask =>
         _viewsToDraw = []


Change approach.

Change approach.

--- lib/Watchers/Template.coffee
+++ lib/Watchers/Template.coffee
@@ -13,9 +13,10 @@
         .substr(1)
         .replace('.jade', '')
         .replace('/template', '')
+        .replace('\\', '/') # Fix for windows.

     getModuleName: (shortPath)->
-        module = shortPath.replace(/[\/\\]/g, '.') + '.template'
+        module = shortPath.replace(/\//g, '.') + '.template'
         if moduleRoot = @getModuleRoot()
             module = "#{moduleRoot}.#{module}"
         module


comments

comments

--- src/ace.coffee
+++ src/ace.coffee
@@ -95,19 +95,27 @@
     if not @editor.selection.isBackwards()
       @editor.selection.selectRight() unless beyondLineEnd(@editor)

+  # inserts a new line at a zero-based row number
   insertNewLine: (row) ->
     @editor.session.doc.insertNewLine row: row, column: 0

+  # move anchor by `columnOffset` columns (can be negative)
   adjustAnchor: (columnOffset) ->
     {row, column} = @editor.selection.getSelectionAnchor()
     @editor.selection.setSelectionAnchor row, column + columnOffset

+  # if the anchor is ahead of the cursor, the selection is backwards
   isSelectionBackwards: -> @editor.selection.isBackwards()

+  # the last zero-based row number
   lastRow: -> @editor.session.getDocument().getLength() - 1

+  # the text that's on `lineNumber` or the current line
   lineText: (lineNumber) -> @editor.selection.doc.getLine lineNumber ? @row()

+  # makes a linewise selection `lines` long if specified or makes the current
+  # selection linewise (by pushing the lead and the anchor to the ends of their
+  # lines)
   makeLinewise: (lines) ->
     {selectionAnchor: {row: anchorRow}, selectionLead: {row: leadRow}} = @editor.selection
     [firstRow, lastRow] = if lines?
@@ -117,6 +125,7 @@
     @editor.selection.setSelectionAnchor firstRow, 0
     @editor.selection.moveCursorTo lastRow + 1, 0

+  ## basic motions (won't clear the selection)
   moveUp:   -> @editor.selection.moveCursorBy -1, 0
   moveDown: -> @editor.selection.moveCursorBy 1, 0
   moveLeft: ->
@@ -126,8 +135,10 @@
     dontMove = if beyond then beyondLineEnd(@editor) else atLineEnd(@editor)
     @editor.selection.moveCursorRight() unless dontMove

+  # move to a zero-based row and column
   moveTo: (row, column) -> @editor.moveCursorTo row, column

+  # puts cursor on the last column of the line
   moveToLineEnd: ->
     {row, column} = @editor.selection.selectionLead
     position = @editor.session.getDocumentLastRowColumnPosition row, column
@@ -137,9 +148,12 @@
     previousRowLength = @editor.session.doc.getLine(previousRow).length
     @editor.selection.moveCursorTo previousRow, previousRowLength

+  # move to first/last line
   navigateFileEnd:   -> @editor.navigateFileEnd()
   navigateLineStart: -> @editor.navigateLineStart()

+  # moves the cursor to the fist char of the matching search or doesn't move at
+  # all
   search: (backwards, needle, wholeWord) ->
     @editor.$search.set {backwards, needle, wholeWord}

@@ -152,6 +166,7 @@
     else if not backwards
       @editor.selection.moveCursorLeft()

+  # delete selected text and return it as a string
   deleteSelection: ->
     yank = @editor.getCopyText()
     @editor.session.remove @editor.getSelectionRange()
@@ -166,6 +181,7 @@
     @editor.blockOutdent()
     @clearSelection()

+  # insert `text` before or `after` the cursor
   insert: (text, after) ->
     @editor.selection.moveCursorRight() if after and not beyondLineEnd(@editor)
     @editor.insert text if text
@@ -174,6 +190,7 @@

   selectionText: -> @editor.getCopyText()

+  # set the selection anchor to the cusor's current position
   setSelectionAnchor: ->
     lead = @editor.selection.selectionLead
     @editor.selection.setSelectionAnchor lead.row, lead.column
@@ -194,6 +211,9 @@
     [row, column]

+  # Selects the line ending at the end of the current line and any whitespace at
+  # the beginning of the next line if `andFollowingWhitespace` is specified.
+  # This is used for the line joining commands `gJ` and `J`.
   selectLineEnding: (andFollowingWhitespace) ->
     @editor.selection.moveCursorLineEnd()
     @editor.selection.selectRight()
@@ -201,11 +221,15 @@
       firstNonBlank = /\S/.exec(@lineText())?.index or 0
       @moveTo @row(), firstNonBlank

+  # Returns the first and the last line that are part of the current selection
   selectionRowRange: ->
     [cursorRow, cursorColumn] = @position()
     {row: anchorRow} = @editor.selection.getSelectionAnchor()
     [Math.min(cursorRow, anchorRow), Math.max(cursorRow, anchorRow)]

+  # Returns the number of chars selected if the selection is one row. If the
+  # selection is multiple rows, it retuns the number of line endings selected
+  # and the number of chars selected on the last row of the selection
   characterwiseSelectionSize: ->
     {selectionAnchor, selectionLead} = @editor.selection
     rowsDown = selectionLead.row - selectionAnchor.row
@@ -216,6 +240,8 @@
       trailingChars: (if rowsDown > 0 then selectionLead else selectionAnchor).column + 1

+# Ace's UndoManager is extended to handle undoing and repeating switches to
+# insert and replace mode
 class JimUndoManager extends UndoManager
   # override so that the default undo (button and keyboard shortcut)
   # will skip over Jim's bookmarks and behave as they usually do
@@ -223,11 +249,13 @@
     @silentUndo() if @isJimMark @lastOnUndoStack()
     super

+  # is this a bookmark we pushed onto the stack or an actual Ace undo entry
   isJimMark: (entry) ->
     typeof entry is 'string' and /^jim:/.test entry

   lastOnUndoStack: -> @$undoStack[@$undoStack.length-1]

+  # pop the item off the stack without doing anything with it
   silentUndo: ->
     deltas = @$undoStack.pop()
     @$redoStack.push deltas if deltas
@@ -236,6 +264,9 @@
     'jim:insert:end':  'jim:insert:start'
     'jim:replace:end': 'jim:replace:start'

+  # If the last command was an insert or a replace ensure that all undo items
+  # associated with that command are undone.  If not, just do a regular ace
+  # undo.
   jimUndo: ->
     lastDeltasOnStack = @lastOnUndoStack()
     if typeof lastDeltasOnStack is 'string' and startMark = @matchingMark[lastDeltasOnStack]
@@ -259,6 +290,13 @@
     else
       @undo()

+  # If the last command was an insert return all text that was inserted taking
+  # backspaces into account.
+  #
+  # If the cursor moved partway through the insert (with arrow keys or with the
+  # mouse), then only the last peice of contiguously inserted text is returned
+  # and `contiguous` is returned as `false`.  This is to match Vim's behavior
+  # when repeating non-contiguous inserts.
   lastInsert: ->
     return '' if @lastOnUndoStack() isnt 'jim:insert:end'

@@ -303,6 +341,8 @@
             return string: stringParts.join(''), contiguous: false
     string: stringParts.join(''), contiguous: true

+
+# cursor and selection styles that Jim uses
 require('pilot/dom').importCssString """
   .jim-normal-mode div.ace_cursor
   , .jim-visual-mode div.ace_cursor {
@@ -316,14 +356,20 @@
   }
 """

+
+# Is the keyboard event a printable character key?
 isCharacterKey = (hashId, keyCode) -> hashId is 0 and not keyCode

+# Sets up Jim to handle the Ace `editor`'s keyboard events
 Jim.aceInit = (editor) ->
   editor.setKeyboardHandler
     handleKeyboard: (data, hashId, keyString, keyCode) ->
       if keyCode is 27 # esc
         jim.onEscape()
       else if isCharacterKey hashId, keyCode
+        # We've made some deletion as part of a change operation already and
+        # we're about to start the actual insert.  Mark this moment in the undo
+        # stack.
         if jim.afterInsertSwitch
           if jim.mode.name is 'insert'
             jim.adaptor.markUndoPoint 'jim:insert:afterSwitch'
@@ -353,4 +399,5 @@
   # to initialize the editor class names
   adaptor.onModeChange null, name: 'normal'

+  # returns `jim` if embedders wanna inspect its state or give it a high five
   jim


check to make sure count handle is defined before stopping

--- lib/datatables.server.coffee
+++ lib/datatables.server.coffee
@@ -95,7 +95,8 @@
       # When the publication is terminated the observers are stopped to prevent memory leaks.
       self.onStop ->
         handle.stop()
-        countHandle.stop()
+        if countHandle
+          countHandle.stop()

   # ##### @debug
   # Debug flag is initialized to false. Set `DataTable.debug = "true"` for all debug messages.


Add download-all command to download all release assets.

--- src/cli.coffee
+++ src/cli.coffee
@@ -42,6 +42,17 @@
               return console.error("Unable to download #{asset.name}") if error?
               stream.pipe fs.createWriteStream(asset.name)

+    when 'download-all'
+      run github, 'list', argv, (error, releases) ->
+        return callback(error) if error?
+        for release in releases
+          do (release) ->
+            for asset in release.assets when asset.state is 'uploaded' and minimatch asset.name, argv.filename
+              do (asset) ->
+                github.downloadAsset asset, (error, stream) ->
+                  return console.error("Unable to download #{asset.name}") if error?
+                  stream.pipe fs.createWriteStream(asset.name)
+
     else
       console.error "Invalid command: #{command}"


Print sessionId if --verbose or -v was provided to  signin

--- src/cli.coffee
+++ src/cli.coffee
@@ -82,6 +82,7 @@

       config.set 'sessionId', sessionId
       console.log chalk.green 'You have signed in successfully'
+      if argv.verbose || argv.v then console.log sessionId

   upload: (argv) ->
     upload = require('./upload')


Add key to each grid item

Add key to each grid item

--- src/index.cjsx
+++ src/index.cjsx
@@ -34,12 +34,15 @@

               if _.isString image
                 imageEl = React.DOM.img({src: image})
+                key = image
               else if _.isObject image
                 imageEl =
+                key = image.src

               return (
                 React.DOM.div({
                   className: "image-wrapper"
+                  key: key
                   style: {
                     width: "#{imageWidth}px"
                     height: "#{imageWidth*@props.widthHeightRatio}px"


Small change for readability

Small change for readability

--- src/index.cjsx
+++ src/index.cjsx
@@ -4,6 +4,7 @@
   displayName: 'SmartTimeAgo'
   render: ->
     thisYear = moment().year() is moment(@props.value).year()
+
     # Show relative dates for tickets < two weeks old and absolute dates for anything
     # older.
     if 2 >= moment().diff(moment(@props.value), 'weeks')


Make path an optional parameter

Make path an optional parameter

--- src/ncn.coffee
+++ src/ncn.coffee
@@ -3,7 +3,9 @@

 class NCN
   constructor: (@host = '') ->
+    @toilets = new NCN @host + '/toilets'
   request = (method) -> (path, data) ->
+    [path, data] = [@host, path] unless data?
     Bacon.fromPromise(
       $.ajax @host + path,
         data: data


Weird

Weird

--- c3/app/assets/javascripts/s4_careYouProvide/g11_break.coffee
+++ c3/app/assets/javascripts/s4_careYouProvide/g11_break.coffee
@@ -7,5 +7,4 @@
       $(this).parent().next().slideDown()
     else
       textArea = $(this).parent().next().find("textarea")
-      $(this).parent().next().slideUp() ->
-        textArea.val ""+      $(this).parent().next().slideUp -> textArea.val("")


Added support for calling afterInsert() immediately after insert().

--- src/Collections.coffee
+++ src/Collections.coffee
@@ -175,7 +175,7 @@

     beforeInsert = args.beforeInsert
     afterInsert = args.afterInsert
-    insert = (srcDoc) ->
+    insert = (srcDoc) =>
       df = Q.defer()
       if beforeInsert
         resultDoc = beforeInsert(srcDoc)
@@ -183,8 +183,23 @@
           return
         else if Types.isObjectLiteral(resultDoc)
           srcDoc = resultDoc
-      dest.insert srcDoc, Promises.toCallback(df)
-      if afterInsert then df.promise.then(afterInsert)
+      # If inserting synchronously is possible, do so to ensure afterInsert is called before
+      # any other code has a chance to run in case this is undesirable.
+      isSync = Meteor.isServer or Collections.isTemporary(dest)
+      if isSync
+        try
+          result = dest.insert(srcDoc)
+          afterInsert?(result)
+          df.resolve(result)
+        catch err
+          df.reject(err)
+      else
+        dest.insert srcDoc, (err, result) ->
+          if err
+            df.reject(err)
+          else
+            afterInsert?(result)
+            df.resolve(result)
       df.promise
     # Collection2 may not allow inserting a doc into a collection with a predefined _id, so we
     # store a map of src to dest IDs. If a copied doc is removed in the destination, this will


Include path instead of file in thrown error

--- src/app/window.coffee
+++ src/app/window.coffee
@@ -127,7 +127,7 @@
   content = fs.read(path)
   if fs.extension(path) == '.less'
     (new less.Parser).parse content, (e, tree) ->
-      throw new Error(e.message, file, e.line) if e
+      throw new Error(e.message, path, e.line) if e
       content = tree.toCSS()

   content


Show stack trace when an extension fails to load

--- src/app/window.coffee
+++ src/app/window.coffee
@@ -79,6 +79,7 @@
       extension
     catch e
       console.error "Failed to load extension named '#{name}'"
+      console.error e.stack

   reload: ->
     if rootView?.getModifiedBuffers().length > 0


Only tag mobile as not supported browser

--- coffee/lib/abstract-chosen.coffee
+++ coffee/lib/abstract-chosen.coffee
@@ -201,9 +201,11 @@

   @browser_is_supported: ->
     if window.navigator.appName == "Microsoft Internet Explorer"
-      return null isnt document.documentMode >= 8
-    if /iP(ad|od|hone)/.test(window.navigator.userAgent)
+      return document.documentMode >= 8
+    if /iP(od|hone)/.test(window.navigator.userAgent)
       return false
+    if /Android/i.test(window.navigator.userAgent)
+      return false if /Mobile/.test(window.navigator.userAgent)
     return true

   @default_multiple_text: "Select Some Options"


Properly handle defaults on single_backstroke_delete

`@options.single_backstroke_delete || true` is _always_ true.

--- coffee/lib/abstract-chosen.coffee
+++ coffee/lib/abstract-chosen.coffee
@@ -29,7 +29,7 @@
     @enable_split_word_search = if @options.enable_split_word_search? then @options.enable_split_word_search else true
     @group_search = if @options.group_search? then @options.group_search else true
     @search_contains = @options.search_contains || false
-    @single_backstroke_delete = @options.single_backstroke_delete || true
+    @single_backstroke_delete = if @options.single_backstroke_delete? then @options.single_backstroke_delete else true
     @max_selected_options = @options.max_selected_options || Infinity
     @inherit_select_classes = @options.inherit_select_classes || false


Wave X released.

Wave X released.

--- coffeescripts/cards-common.coffee
+++ coffeescripts/cards-common.coffee
@@ -2,9 +2,6 @@
 exportObj = exports ? this

 exportObj.unreleasedExpansions = [
-    "Sabine's TIE Fighter Expansion Pack"
-    "Upsilon-class Shuttle Expansion Pack"
-    "Quadjumper Expansion Pack"
     "C-ROC Cruiser Expansion Pack"
 ]


Removed empty line

Removed empty line

--- public/app/common/components/controls/button/button.cjsx
+++ public/app/common/components/controls/button/button.cjsx
@@ -11,7 +11,6 @@
   ]

   render: ->
-
     className = cx
       'cc-button': true,
       secondary: this.props.secondary,


Wave 6 is released!

Wave 6 is released!

--- coffeescripts/cards-common.coffee
+++ coffeescripts/cards-common.coffee
@@ -2,10 +2,6 @@
 exportObj = exports ? this

 exportObj.unreleasedExpansions = [
-    "StarViper Expansion Pack"
-    "M3-A Interceptor Expansion Pack"
-    "IG-2000 Expansion Pack"
-    "Most Wanted Expansion Pack"
     "Imperial Raider Expansion Pack"
 ]


Client: Activity: Feed: Added FeedThreadSidebar to threadpane

--- client/activity/lib/components/channelthreadpane/index.coffee
+++ client/activity/lib/components/channelthreadpane/index.coffee
@@ -11,6 +11,8 @@
 ChannelDropContainer = require 'activity/components/channeldropcontainer'
 getGroup             = require 'app/util/getGroup'
 isFeedEnabled        = require 'app/util/isFeedEnabled'
+FeedThreadSidebar    = require 'activity/components/publicfeedpane/feedthreadsidebar'
+

 module.exports = class ChannelThreadPane extends React.Component

@@ -97,9 +99,17 @@

     return null  unless thread = @state.channelThread

-    <ThreadSidebar
-      channelThread={thread}
-      channelParticipants={@state.channelParticipants} />
+    if not isFeedEnabled()
+      <aside className='ChannelThreadPane-sidebar'>
+        <ThreadSidebar
+          channelThread={thread}
+          channelParticipants={@state.channelParticipants} />
+      </aside>
+    else
+      <aside className='FeedThreadPane-sidebar'>
+        <FeedThreadSidebar
+          channelThread={thread} />
+      </aside>

   render: ->
@@ -111,9 +121,7 @@
         {@renderHeader()}
         {@renderBody()}
       </ChannelDropContainer>
-      <aside className='ChannelThreadPane-sidebar'>
-        {@renderSidebar()}
-      </aside>
+      {@renderSidebar()}
       {@props.children}
     </div>


activity: channel thread can render children

--- client/activity/lib/components/channelthreadpane/index.coffee
+++ client/activity/lib/components/channelthreadpane/index.coffee
@@ -112,6 +112,8 @@
           channelThread={@state.channelThread}
           channelParticipants={@state.channelParticipants}/>
       </aside>
+
+      {@props.children}
     </div>


activity: fix code style problem

activity: fix code style problem

--- client/activity/lib/flux/createchannel/actions/channel.coffee
+++ client/activity/lib/flux/createchannel/actions/channel.coffee
@@ -48,7 +48,7 @@
  * @param {string} payload.purpose
  * @param {array} payload.recipients
 ###
-createPublicChannel = (options={}) ->
+createPublicChannel = (options = {}) ->

   { CREATE_PUBLIC_CHANNEL
     CREATE_PUBLIC_CHANNEL_FAIL
@@ -75,7 +75,7 @@
  * @param {string} payload.purpose
  * @param {array}  payload.recipients
 ###
-createPrivateChannel = (options={}) ->
+createPrivateChannel = (options = {}) ->

   { CREATE_PRIVATE_CHANNEL
     CREATE_PRIVATE_CHANNEL_FAIL


MachineStateModal: human readable status messages

--- client/app/lib/providers/environmentsmachinestatemodal.coffee
+++ client/app/lib/providers/environmentsmachinestatemodal.coffee
@@ -401,10 +401,20 @@
     @createStatusOutput response

+  statusMessagesMap  =
+    'start started'  : 'Starting VM'
+    'stop started'   : 'Stopping VM'
+    'stop finished'  : 'VM is stopped'
+    'start finished' : 'VM is ready'
+
   createStatusOutput: (response) ->

     message = response?.message
-    message = message.capitalize()  if typeof message is 'string'
+
+    if typeof message is 'string'
+      message = statusMessagesMap[message] or message
+      message = message.replace 'machine', 'VM'
+      message = message.capitalize()

     if @logView
       @logView.updatePartial message
@@ -417,7 +427,7 @@

-  getStateLabel:->
+  getStateLabel: ->

     stateTexts       =
       Stopped        : if @isManaged then 'is not reachable.' else 'is turned off.'


Client: Site.Landing: Added new logo to header as svg object

--- client/landing/site.landing/coffee/core/mainheaderview.coffee
+++ client/landing/site.landing/coffee/core/mainheaderview.coffee
@@ -35,7 +35,7 @@
     @addSubView @logo = headerLogo or new kd.CustomHTMLView
       tagName   : 'a'
       cssClass  : 'koding-header-logo'
-      partial   : '<cite></cite>'
+      partial   : '<object type="image/svg+xml" data="/a/images/logos/header_logo.svg" class="main-header-logo">Koding Logo</object>'
       click     : (event) ->
         kd.utils.stopDOMEvent event
         kd.singletons.router.handleRoute '/'


Fix hubot update script

Fix hubot update script

--- vagrant/provision/roles/hubot/files/ninjabot/scripts/update.coffee
+++ vagrant/provision/roles/hubot/files/ninjabot/scripts/update.coffee
@@ -10,7 +10,7 @@
     msg.send "Updating myself. Thanks for your patience..."

     @exec = require('child_process').exec
-    @exec "wget -nv -O /tmp/hubot.$$.zip https://github.com/devops-dojo/the-app/archive/master.zip && unzip -o /tmp/hubot.$$.zip 'the-app-master/vagrant/provision/roles/hubot/files/ninjabot/*' -d /tmp/hubot.$$ && cp -R /tmp/hubot.$$/the-app-master/vagrant/provision/roles/hubot/files/ninjabot . && rm -fR /tmp/hubot.$$ && rm /tmp/hubot.$$.zip", (error, stdout, stderr) ->
+    @exec "wget -nv -O /tmp/hubot.$$.zip https://github.com/devops-dojo/the-app/archive/master.zip && unzip -o /tmp/hubot.$$.zip 'the-app-master/vagrant/provision/roles/hubot/files/ninjabot/*' -d /tmp/hubot.$$ && cp -R /tmp/hubot.$$/the-app-master/vagrant/provision/roles/hubot/files/ninjabot /home/hubot && rm -fR /tmp/hubot.$$ && rm /tmp/hubot.$$.zip", (error, stdout, stderr) ->
       if stdout? && stdout != ''
         msg.send ":tada: Update done! Restarting myself..."
         setTimeout () ->


hackathon: made another static prize into db call.

--- client/landing/site.hackathon/coffee/home/AppViewFull.coffee
+++ client/landing/site.hackathon/coffee/home/AppViewFull.coffee
@@ -414,7 +414,7 @@

What are the prizes?

-        Total prizes over $25,000! Here are the details.
+        Total prizes over $#{prize.toLocaleString()}! Here are the details.

$10,000 cash prize from Koding, split amongst the top 3 teams ($5k, $3k, $2k each).

Winners will be offered credits for services like DigitalOcean, Tutum and many more!


Fix spacing issue

Fix spacing issue

--- client/stacks/lib/views/stacks/basestacktemplatesview.coffee
+++ client/stacks/lib/views/stacks/basestacktemplatesview.coffee
@@ -44,8 +44,8 @@

       stackTemplateId = params.contents

-      items   = @initialView.stackTemplateList.listController.getListItems()
-      [item]  = items.filter (i) -> i.getData()._id is stackTemplateId
+      items  = @initialView.stackTemplateList.listController.getListItems()
+      [item] = items.filter (i) -> i.getData()._id is stackTemplateId

       { listController } = @initialView.stackTemplateList


cleaning up

cleaning up

--- examples/basic/src/toplevel.coffee
+++ examples/basic/src/toplevel.coffee
@@ -1,3 +1,2 @@
 class TopLevel
-	constructor:->
-		console.log "TopLevel created!"+	constructor:-> console.log "TopLevel created!"


Port GetFeaturesYAPE to Process API

Port GetFeaturesYAPE to Process API

--- components/GetFeaturesYAPE.coffee
+++ components/GetFeaturesYAPE.coffee
@@ -1,32 +1,21 @@
 noflo = require 'noflo'
 jsfeat = require 'jsfeat'

-class GetFeaturesYAPE extends noflo.Component
-  description: 'Extract feature corners of image (method: YAPE)'
-  icon: 'file-image-o'
-  constructor: ->
-
-    @inPorts =
-      canvas: new noflo.Port 'object'
-    @outPorts =
-      corners: new noflo.Port 'array'
-      canvas: new noflo.Port 'object'
-
-    @inPorts.canvas.on 'begingroup', (group) =>
-      @outPorts.corners.beginGroup group
-      @outPorts.canvas.beginGroup group
-    @inPorts.canvas.on 'endgroup', (group) =>
-      @outPorts.corners.endGroup group
-      @outPorts.canvas.endGroup group
-    @inPorts.canvas.on 'disconnect', () =>
-      @outPorts.corners.disconnect()
-      @outPorts.canvas.disconnect()
-    @inPorts.canvas.on 'data', (canvas) =>
-      corners = @getCorners canvas
-      @outPorts.corners.send corners
-      @outPorts.canvas.send canvas
-
-  getCorners: (canvas) ->
+exports.getComponent = ->
+  c = new noflo.Component
+  c.description = 'Extract feature corners of image (method: YAPE)'
+  c.icon = 'file-image-o'
+  c.inPorts.add 'canvas',
+    datatype: 'object'
+  c.outPorts.add 'corners',
+    datatype: 'array'
+  c.outPorts.add 'canvas',
+    datatype: 'object'
+  c.forwardBrackets =
+    canvas: ['canvas', 'corners']
+  c.process (input, output) ->
+    return unless input.hasData 'canvas'
+    canvas = input.getData 'canvas'
     context = canvas.getContext '2d'
     img = context.getImageData 0, 0, canvas.width, canvas.height

@@ -43,9 +32,8 @@
     for i in [0...pixels]
       corners.push new jsfeat.keypoint_t 0,0,0,
     count = jsfeat.yape06.detect img_u8, corners
-    return corners.slice 0, count

-exports.getComponent = -> new GetFeaturesYAPE
-
-
-
+    output.sendDone
+      corners: corners.slice 0, count
+      canvas: canvas
+    return


Publish a sign up success event over the mediator

--- components/auth_modal/view.coffee
+++ components/auth_modal/view.coffee
@@ -119,6 +119,7 @@
           else
             location.reload()
         when 'register'
+          mediator.trigger 'auth:sign_up:success'
           location.href = @redirectTo or '/personalize'
         when 'forgot'
           mediator.trigger 'auth:change:mode', 'reset'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -111,7 +111,7 @@
   if min? and max?
     Math.min(Math.max(this, min), max)
   else if min?
-    Math.max(this, min)
+    Math.ma(this, min)
   else if max?
     Math.max(this, max)
   else


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -74,7 +74,7 @@
   Math.round(this)

 ###*
-Get a bunch o
+Get a bunch of points eq

 4.circularPoints (p) ->


catch api error responses.

catch api error responses.

--- src/client/api.coffee
+++ src/client/api.coffee
@@ -27,7 +27,7 @@
   message: ()=>
     return console.error("Hull Api is not initialized yet. You should run your app from the callback of Hull.ready()") unless @channel.rpc
     {opts, callback, errback} = parseOpts(Array.prototype.slice.call(arguments))
-    new Promise (resolve, reject)=>
+    p = new Promise (resolve, reject)=>

       onSuccess = (res={})=>
         @updateCurrentUserCookies(res.headers, res.provider)
@@ -39,6 +39,9 @@
         reject(response.message, error)

       @channel.rpc.message(opts, onSuccess, onError)
+
+    p.catch (err)->
+      console.warn err, err.stack

   clearToken    : (args...)=>
     @channel.rpc.clearUserToken(args...) # No need to be exposed, IMO


Fix nasty one

Fix nasty one

--- src/client/api.coffee
+++ src/client/api.coffee
@@ -238,7 +238,7 @@
         sync: sync
         url: ->
           if (@id || @_id)
-            url = normalizeAPIArguments([@id || @_id])[0]
+            url = normalizeAPIArguments([@_id || @id])[0]
           else
             url = @collection?.url
           url


move shopping cart to top right corner

move shopping cart to top right corner

--- public/views/widget_view.coffee
+++ public/views/widget_view.coffee
@@ -26,6 +26,9 @@
       }
       .epages-shop-product-list {
         clear: both;
+      }
+      .epages-shop-navigation > .epages-shop-cart {
+        float: right;
       }
       .epages-cart-button {
         background: url(#{App.rootUrl}/images/cart.png) left center no-repeat;


Patch access_token also for other subcommands

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -193,13 +193,9 @@

         return _subCommandHelp('state') unless program.projectKey

-        ProjectCredentialsConfig.create()
+        @_ensureCredentials(program)
         .then (credentials) =>
-          options =
-            config: credentials.enrichCredentials
-              project_key: program.projectKey
-              client_id: program.clientId
-              client_secret: program.clientSecret
+          options = _.extend credentials,
             timeout: program.timeout
             show_progress: true
             user_agent: "#{package_json.name} - State - #{package_json.version}"
@@ -286,20 +282,17 @@
       .option '--fillAllRows', 'When given product attributes like name will be added to each variant row.'
       .option '--categoryBy <name>', 'Define which identifier should be used to for the categories column - either slug or externalId. If nothing given the named path is used.'
       .usage '--projectKey <project-key> --clientId <client-id> --clientSecret <client-secret> --template <file> --out <file>'
-      .action (opts) ->
+      .action (opts) =>
         GLOBALS.DEFAULT_LANGUAGE = opts.language

         return _subCommandHelp('export') unless program.projectKey
-        ProjectCredentialsConfig.create()
+
+        @_ensureCredentials(program)
         .then (credentials) ->
           options =
             fillAllRows: opts.fillAllRows
             categoryBy: opts.categoryBy
-            client:
-              config: credentials.enrichCredentials
-                project_key: program.projectKey
-                client_id: program.clientId
-                client_secret: program.clientSecret
+            client: _.extend credentials,
               timeout: program.timeout
               user_agent: "#{package_json.name} - Export - #{package_json.version}"
             export:
@@ -355,18 +348,14 @@
       .option '-l, --languages [lang,lang]', 'List of languages to use for template (default is [en])', @_list, ['en']
       .option '--all', 'Generates one template for all product types - if not given you will be ask which product type to use'
       .usage '--projectKey <project-key> --clientId <client-id> --clientSecret <client-secret> --out <file>'
-      .action (opts) ->
+      .action (opts) =>

         return _subCommandHelp('template') unless program.projectKey

-        ProjectCredentialsConfig.create()
+        @_ensureCredentials(program)
         .then (credentials) ->
           options =
-            client:
-              config: credentials.enrichCredentials
-                project_key: program.projectKey
-                client_id: program.clientId
-                client_secret: program.clientSecret
+            client: credentials
             timeout: program.timeout
             show_progress: true
             user_agent: "#{package_json.name} - Template - #{package_json.version}"


dont fail on new line

dont fail on new line

--- src/coffeelint.coffee
+++ src/coffeelint.coffee
@@ -675,7 +675,7 @@

     lintColonAssignment : (token) ->
         leftSpaced = token[2].first_column - @peek(-1)?[2].last_column is 2
-        if token.spaced and leftSpaced
+        if (token.spaced or token.newLine) and leftSpaced
             null
         else
             @createLexError('colon_assignment_spacing')


Adding a upsert function

Adding a upsert function

Create element, check if it already exists and update it if so.

--- src/collection.coffee
+++ src/collection.coffee
@@ -46,6 +46,13 @@
 			element['$updated'] = date
 			@items.push element
 		@save() if @autosave
+		return @items
+
+	upsert: (element, key, value) ->
+		if this.where("(@" + key + " ==  '" + value + "')").length > 0
+			this.update check[0].cid, element
+		else
+			this.insert element
 		return @items

 	# Retrieving data functions


Improve feedback for starting an app

Improve feedback for starting an app

--- src/application.coffee
+++ src/application.coffee
@@ -69,10 +69,13 @@
 				'/dev': {}
 		)
 	.then (container) ->
+		console.log('Starting container:', app)
 		container.startAsync(
 			Privileged: true
 			Binds: ['/dev:/dev']
 		)
+	.tap ->
+		console.log('Started container:', app)

 exports.restart = restart = (app) ->
 	kill(app)


Unfocus before setValue to fix setValue bug

--- src/controller.coffee
+++ src/controller.coffee
@@ -3621,6 +3621,7 @@
     @aceEditor.session.setScrollTop oldScrollTop

     if @currentlyUsingBlocks
+      @setTextInputFocus null
       result = @setValue_raw value
       if result.success is false
         @setEditorState false


expose changePaletteGroup method, allow groups to have ids

--- src/controller.coffee
+++ src/controller.coffee
@@ -1834,8 +1834,10 @@
         paletteHeaderRow.style.height = 0

     # Create the element itself
-    paletteGroupHeader = document.createElement 'div'
+    paletteGroupHeader = paletteGroup.header = document.createElement 'div'
     paletteGroupHeader.className = 'droplet-palette-group-header'
+    if paletteGroup.id
+      paletteGroupHeader.id = 'droplet-palette-group-header-' + paletteGroup.id
     paletteGroupHeader.innerText = paletteGroupHeader.textContent = paletteGroupHeader.textContent = paletteGroup.name # innerText and textContent for FF compatability
     if paletteGroup.color
       paletteGroupHeader.className += ' ' + paletteGroup.color
@@ -1854,32 +1856,12 @@
         title: data.title
         id: data.id

-    paletteGroupBlocks = newPaletteBlocks
+    paletteGroup.parsedBlocks = newPaletteBlocks

     # When we click this element,
     # we should switch to it in the palette.
     updatePalette = =>
-      # Record that we are the selected group now
-      @currentPaletteGroup = paletteGroup.name
-      @currentPaletteBlocks = paletteGroupBlocks
-      @currentPaletteMetadata = paletteGroupBlocks
-
-      # Unapply the "selected" style to the current palette group header
-      @currentPaletteGroupHeader?.className =
-          @currentPaletteGroupHeader.className.replace(
-              /\s[-\w]*-selected\b/, '')
-
-      # Now we are the current palette group header
-      @currentPaletteGroupHeader = paletteGroupHeader
-      @currentPaletteIndex = i
-
-      # Apply the "selected" style to us
-      @currentPaletteGroupHeader.className +=
-          ' droplet-palette-group-header-selected'
-
-      # Redraw the palette.
-      @rebuildPalette()
-      @fireEvent 'selectpalette', [paletteGroup.name]
+      @changePaletteGroup paletteGroup

     clickHandler = =>
       do updatePalette
@@ -1893,6 +1875,37 @@

   @resizePalette()
   @resizePaletteHighlight()
+
+Editor::changePaletteGroup = (group) ->
+  for curGroup, i in @paletteGroups
+    if group is curGroup or group is curGroup.id or group is curGroup.name
+      paletteGroup = curGroup
+      break
+
+  if not paletteGroup
+    return
+
+  # Record that we are the selected group now
+  @currentPaletteGroup = paletteGroup.name
+  @currentPaletteBlocks = paletteGroup.parsedBlocks
+  @currentPaletteMetadata = paletteGroup.parsedBlocks
+
+  # Unapply the "selected" style to the current palette group header
+  @currentPaletteGroupHeader?.className =
+      @currentPaletteGroupHeader.className.replace(
+          /\s[-\w]*-selected\b/, '')
+
+  # Now we are the current palette group header
+  @currentPaletteGroupHeader = paletteGroup.header
+  @currentPaletteIndex = i
+
+  # Apply the "selected" style to us
+  @currentPaletteGroupHeader.className +=
+      ' droplet-palette-group-header-selected'
+
+  # Redraw the palette.
+  @rebuildPalette()
+  @fireEvent 'selectpalette', [paletteGroup.name]

 # The next thing we need to do with the palette
 # is let people pick things up from it.


Fix template names when walkdir returns absolute paths.

--- src/cli/command.coffee
+++ src/cli/command.coffee
@@ -1,6 +1,7 @@
 CoffeeScript  = require 'coffee-script'
 CoffeeMaker   = require './coffee-maker'
 fs            = require 'fs'
+path          = require 'path'
 walkdir       = require 'walkdir'

 red   = '\u001b[31m'
@@ -185,6 +186,10 @@

           # Loop through all Haml files and compile them
           for filename in walkdir.sync baseDir
+            # walkdir sometimes returns absolute paths
+            if filename.substring(0, 1) is '/'
+              filename = path.relative(baseDir, filename)
+
             if filename.match /([^\.]+)(\.html)?\.haml[c]?$/

               # Combine all files into a single output


It Is Now Possible to Take On Jackie's Lead

--- src/coffee/game.coffee
+++ src/coffee/game.coffee
@@ -2,6 +2,10 @@
   illusionist: 'an illusionist'
   hallucinist: 'a hallucinist'
   hypnotist: 'a hypnotist'
+
+leads =
+  none: undefined
+  jackie: 'Jackie'

 angular.module 'gameDefinition', ['qbn.edsl', 'qbn.quality']
   .run (qbnEdsl) ->
@@ -20,6 +24,12 @@
       undefined
       value: 1

+    quality 'lead',
+      () -> "Current Lead: #{@value}"
+      'Your current lead'
+      undefined
+      value: leads.none
+
     quality 'illusionHunch', qualityType.item,
       'Illusion Hunch'
       '''
@@ -68,9 +78,12 @@

     ## Special Items

-    quality 'jackieReceipt', qualityType.item,
-      'Receipt from Jackie'
-      'Proof that Mr. Brown hired a private detective before his death.'
+    quality 'jackie',
+      () ->
+        switch @value
+          when 1 then 'Mr. Brown hired a private eye before his disappearance'
+          when 2 then 'Jackie has asked you to find his partner'
+      '!!ERROR!!', undefined

     quality 'roxyThugDescription', qualityType.item,
       'Description of Roxy\'s Thugs'
@@ -233,6 +246,8 @@
       '''
       You know a few places to start looking for info.
       '''
+      visible:
+        lead: reqs.notexists

     front choice 'rest',
       'Return home to rest'
@@ -436,7 +451,7 @@
           You feel like maybe there's something she hasn't told you yet.
           '''
           visible:
-            jackieReceipt: reqs.lte 0
+            jackie: reqs.lte 0
         choice 'askClient2',
           'Ask what\'s wrong'
           '''
@@ -491,7 +506,9 @@
       competition? It's not much, but it's a start.
       '''
       consequences:
-        jackieReceipt: consq.increase 1
+        jackie: (quality) ->
+          quality.value++
+          'You have received a receipt from Mrs. Brown.'
         bother: setBothered

     storylet 'askClient2',
@@ -604,7 +621,7 @@
           'Jackie, old-school detective'
           'Mr. Brown hired Jackie to do something. What? And why?'
           visible:
-            jackieReceipt: reqs.gte 1
+            jackie: reqs.gte 1
         choice 'roxyStart',
           'Roxy Malone, big-time gangster'
           'Roxy is trying to intimidate Mrs. Brown. But why?'
@@ -709,6 +726,11 @@
       detective-client confidentiality for a bit as regards the quite-likely-late Mr. Brown and his
       concerns."
       '''
+      consequences:
+        lead: consq.set leads.jackie
+        jackie: (quality) ->
+          quality.value++
+          'Jackie has asked you to find his partner.'

     ## The Salamander Club
     storylet 'clubEntry',


Update jQuery and CoffeeScript versions, use CDNJS instead.

--- src/coffeetable.coffee
+++ src/coffeetable.coffee
@@ -25,8 +25,8 @@
     autoload_coffee_script   : true
     autoload_jquery          : true
     # URLs of CoffeeScript and jQuery files to load
-    coffeescript_js : 'http://code.alecperkins.net/coffeetable/lib/coffee_script-1.1.1-min.js'
-    jquery_js       : 'http://code.alecperkins.net/coffeetable/lib/jquery-1.6.2-min.js'
+    coffeescript_js : 'http://cdnjs.cloudflare.com/ajax/libs/coffee-script/1.2.0/coffee-script.min.js'
+    jquery_js       : 'http://cdnjs.cloudflare.com/ajax/libs/jquery/1.7.1/jquery.min.js'

     # Persist the history using localStorage
     local_storage   : true


added complet flag

added complet flag

--- src/data/table.coffee
+++ src/data/table.coffee
@@ -321,8 +321,8 @@
     (new data.ops.BlockProject @, mappings, extend, blocksize)

   # @param alias name of the table column that will store the partitions
-  partition: (cols, alias="table") ->
-    (new data.ops.Partition @, cols, alias)
+  partition: (cols, alias="table", complete=no) ->
+    (new data.ops.Partition @, cols, alias, complete)

   flatten: ->
     new data.ops.Flatten @
@@ -331,9 +331,9 @@
   aggregate: (aggs, alias=null) ->
     new data.ops.Aggregate @, aggs, alias

-  groupby: (cols, aggs) ->
+  groupby: (cols, aggs, complete=no) ->
     new data.ops.Aggregate(
-      @partition(cols),
+      @partition(cols, complete),
       aggs)

   partitionJoin: (table, cols, type="outer") ->


Only fire "cancel" event when the datepicker is shown

This is to prevent datepickers from firing the “cancel” all the time

--- src/datepicker.coffee
+++ src/datepicker.coffee
@@ -20,7 +20,7 @@
         model.set "show", true, =>
           @emit "show"
     dom.on "mousedown", (e) =>
-      unless @parent.contains(e.target)
+      unless @parent.contains(e.target) or not model.get "show"
         model.set "show", false, =>
           @emit "cancel"


minor changes in decoration.coffee

minor changes in decoration.coffee

--- src/decoration.coffee
+++ src/decoration.coffee
@@ -20,7 +20,7 @@
 # decoration = editor.decorateMarker(marker, {type: 'line', class: 'my-line-class'})
 # ```
 #
-# Best practice for destorying the decoration is by destroying the {Marker}.
+# Best practice for destroying the decoration is by destroying the {Marker}.
 #
 # ```coffee
 # marker.destroy()
@@ -56,7 +56,6 @@
     @properties.id = @id
     @flashQueue = null
     @destroyed = false
-
     @markerDestroyDisposable = @marker.onDidDestroy => @destroy()

   # Essential: Destroy this marker.


Don't start ignored participants

Don't start ignored participants

--- src/coordinator.coffee
+++ src/coordinator.coffee
@@ -212,6 +212,9 @@
     return @library.getSource component, callback

   startParticipant: (node, component, callback) ->
+    if node in @options.ignore
+      console.log "WARNING: Not restarting ignored participant #{node}"
+      return callback null
     if not @library.components[component]?.command
       console.log "WARNING: Attempting to start participant with missing component: #{node}(#{component})"
       # XXX: should be an error, but Flowhub does this in project mode..


XMPP adapter: fix 406 'Only occupants are allowed to send messages to the conference' error

--- src/hubot/xmpp.coffee
+++ src/hubot/xmpp.coffee
@@ -131,13 +131,17 @@
     for str in strings
       console.log "Sending to #{user.room}: #{str}"

-      to = if user.type in ['direct', 'chat'] then "#{user.room}/#{user.id}" else user.room
+      params =
+        to: if user.type in ['direct', 'chat'] then "#{user.room}/#{user.id}" else user.room
+        type: user.type
+
+      switch user.type
+        when 'chat'
+          params.from = "#{@options.username}/#{@name}"
+        when 'direct'
+          params.from = @options.username

-      message = new Xmpp.Element('message',
-                  from: @options.username
-                  to: to
-                  type: user.type
-                ).
+      message = new Xmpp.Element('message', params).
                 c('body').t(str)

       @client.send message
@@ -150,7 +154,6 @@
     string = strings.join "\n"

     message = new Xmpp.Element('message',
-                from: @options.username,
                 to: user.room
                 type: user.type
               ).


Fix lint errors.

Fix lint errors.

--- src/init/index.coffee
+++ src/init/index.coffee
@@ -6,7 +6,7 @@

 module.exports = class Generator extends base

-Generator::askConfig = () ->
+Generator::askConfig = ->
 	done = @async()
 	prompts = [
 		{


Use beatmapset data for mapper name and id

--- resources/assets/coffee/react/_components/beatmapset-mapping.coffee
+++ resources/assets/coffee/react/_components/beatmapset-mapping.coffee
@@ -25,8 +25,10 @@

 export class BeatmapsetMapping extends React.PureComponent
   render: =>
-    user = @props.user ? @props.beatmapset.user
-
+    user =
+      id: @props.beatmapset.user_id
+      username: @props.beatmapset.creator
+      avatar_url: (@props.user ? @props.beatmapset.user)?.avatar_url

     div className: bn,
       if user.id?


Check response exists before calling method

--- services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
+++ services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
@@ -341,7 +341,8 @@
 					response = getChkTex()

 			# display the combined result
-			response.finally annotateFiles
+			if response?
+				response.finally annotateFiles

 		getRootDocOverride_id = () ->
 			doc = ide.editorManager.getCurrentDocValue()


Change mediator tests

Change mediator tests

--- test/integration/mediatorAPITests.coffee
+++ test/integration/mediatorAPITests.coffee
@@ -22,7 +22,7 @@
           type: 'http'
         }
       ]
-      defaultChannelConfig: [
+      defaultChannelConfig: [{
         name: "Save Encounter 1"
         urlPattern: "/encounters"
         type: 'http'
@@ -35,7 +35,7 @@
             type: 'http'
           }
         ]
-      ,
+      }, {
         name: "Save Encounter 2"
         urlPattern: "/encounters2"
         type: 'http'
@@ -48,7 +48,7 @@
             type: 'http'
           }
         ]
-      ]
+      }]

     mediator2 =
       urn: "urn:uuid:25ABAB99-23BF-4AAB-8832-7E07E4EA5902"
@@ -1145,7 +1145,8 @@
               done()

       it 'should add all channels in the defaultChannelConfig property', (done) ->
-        new Mediator(mediator1).save ->
+        new Mediator(mediator1).save (err) ->
+          return done err if err
           request("https://localhost:8080")
             .post("/mediators/urn:uuid:EEA84E13-1C92-467C-B0BD-7C480462D1ED/channels")
             .set("auth-username", testUtils.rootUser.email)
@@ -1155,15 +1156,13 @@
             .send([])
             .expect(201)
             .end (err, res) ->
-              if err
-                done err
-              else
-                Channel.find {}, (err, channels) ->
-                  done err if err
-                  channels.length.should.be.exactly 2
-                  channels[0].name.should.be.exactly 'Save Encounter 1'
-                  channels[1].name.should.be.exactly 'Save Encounter 2'
-                  done()
+              return done err if err
+              Channel.find {}, (err, channels) ->
+                return done err if err
+                channels.length.should.be.exactly 2
+                channels[0].name.should.be.exactly 'Save Encounter 1'
+                channels[1].name.should.be.exactly 'Save Encounter 2'
+                done()

       it 'should add selected channels in the defaultChannelConfig property if the body is set (save one)', (done) ->
         new Mediator(mediator1).save ->


Add error message on failure and abort on unmount

--- resources/assets/coffee/react/_components/comment.coffee
+++ resources/assets/coffee/react/_components/comment.coffee
@@ -42,11 +42,17 @@
   constructor: (props) ->
     super props

+    @xhr = {}
+
     @state =
       postingVote: false
       editing: false
       showNewReply: false
       expandReplies: !@isDeleted()
+
+
+  componentWillUnmount: =>
+    xhr?.abort() for own _name, xhr of @xhr

   render: =>
@@ -327,10 +333,14 @@
   delete: =>
     return unless confirm(osu.trans('common.confirmation'))

-    $.ajax laroute.route('comments.destroy', comment: @props.comment.id),
+    @xhr.delete = $.ajax laroute.route('comments.destroy', comment: @props.comment.id),
       method: 'DELETE'
     .done (data) =>
       $.publish 'comment:updated', comment: data
+    .fail (xhr, status) =>
+      return if status == 'abort'
+
+      osu.ajaxError xhr

   toggleEdit: =>
@@ -372,10 +382,14 @@

   restore: =>
-    $.ajax laroute.route('comments.restore', comment: @props.comment.id),
+    @xhr.restor = $.ajax laroute.route('comments.restore', comment: @props.comment.id),
       method: 'POST'
     .done (data) =>
       $.publish 'comment:updated', comment: data
+    .fail (xhr, status) =>
+      return if status == 'abort'
+
+      osu.ajaxError xhr

   toggleNewReply: =>
@@ -392,13 +406,17 @@
       method = 'POST'
       voteAction = 'added'

-    $.ajax laroute.route('comments.vote', comment: @props.comment.id),
+    @xhr.vote = $.ajax laroute.route('comments.vote', comment: @props.comment.id),
       method: method
     .always =>
       @setState postingVote: false
     .done (data) =>
       $.publish 'comment:updated', comment: data
       $.publish "commentVote:#{voteAction}", id: @props.comment.id
+    .fail (xhr, status) =>
+      return if status == 'abort'
+
+      osu.ajaxError xhr

   closeNewReply: =>


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/core_object.coffee
+++ src/core_object.coffee
@@ -147,7 +147,7 @@
           moduleName = Module
           Module = Module.constantize()
         else
-          #
+          # Attempt to look up module
           for key, value of root
             if value is Module
               moduleName = key


TAP5-2246: Use CofeeScript list comprehension to log init arguments, fixes JavaScript error in IE8

--- tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/pageinit.coffee
+++ tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/pageinit.coffee
@@ -102,7 +102,7 @@
           throw new Error "Could not locate function `#{qualifiedName}'."

         if console.debugEnabled
-          argsString = _.map(initArguments, JSON.stringify).join(", ")
+          argsString = (JSON.stringify arg for arg in initArguments).join(", ")
           console.debug "Invoking #{qualifiedName}(#{argsString})"

         fn.apply null, initArguments


Create counter issue

Create counter issue

--- server/src/Component/Builtin/Database/CouchMuffin.coffee
+++ server/src/Component/Builtin/Database/CouchMuffin.coffee
@@ -100,6 +100,9 @@
         options = params.options || {}
         validate = options.validate ? true
         match = options.match ? true
+        idWithPrefix = "#{@_keyPrefix}#{id}"
+
+        return callback error: 'InvalidId' if id?

         afterValidate = (error = null) =>
             return callback(error) if error
@@ -108,7 +111,7 @@
                 matched = @_validator.match data
                 return callback name: 'MatchFailed', fields: matched unless matched is true

-            @_dataSource.bucket.upsert id, data, options, (error, result) ->
+            @_dataSource.bucket.upsert idWithPrefix, data, options, (error, result) ->
                 return callback error if error
                 return callback null, result


Set url once

Set url once

--- resources/assets/coffee/_classes/current-user-observer.coffee
+++ resources/assets/coffee/_classes/current-user-observer.coffee
@@ -31,8 +31,8 @@
   setCovers: (elements) =>
     elements ?= @covers

+    url = if currentUser.id? then "url('#{currentUser.cover.url}')"
     for el in elements
-      url = if currentUser.id? then "url('#{currentUser.cover.url}')"
       el.style.backgroundImage = url


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/function_extensions.coffee
+++ test/function_extensions.coffee
@@ -22,6 +22,7 @@
   ok calledAfter, "Called after"

 test "#once", ->
+  s
   onceScore = ->
     (  ->
     ).once()


Fix for flexibility freeze

Fix for flexibility freeze

--- app/assets/javascripts/lib/views/html_table_chart_view.coffee
+++ app/assets/javascripts/lib/views/html_table_chart_view.coffee
@@ -39,14 +39,11 @@
   # is the result of a gquery and write the output
   #
   fill_cells: ->
-    container_node_data = @container_node().find('.chart').data()
-    default_decimals    = container_node_data.decimals
-    default_decimals    = 1 unless _.isNumber(default_decimals)
+    default_decimals = @container_node().find('.chart').data('decimals') || 1

     for cell in @dynamic_cells()
       gqid = $(cell).data('gquery')
-      decimals = $(cell).data('decimals')
-      decimals = default_decimals unless _.isNumber(decimals)
+      decimals = $(cell).data('decimals') || default_decimals
       graph = $(cell).data('graph') || 'future'
       serie = @model.series.with_gquery(gqid)
       if !serie


fix new conversation autocomplete

fix new conversation autocomplete

fixes CNVS-10686

test plan:
  * in new conversations, perform a search that returns a
    course;
  * click on the course result and verify that its results
    are displayed in place of the original search results
    and not in addition to those results.

Change-Id: Ie33b09fd6ae08969b225a54bbbddeb2161857828
Reviewed-on: https://gerrit.instructure.com/29016
Reviewed-by: Braden Anderson <a6cc2cef7d25f27847876bdb81651755fcebacf0@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
QA-Review: Steven Shepherd <e794458448443812fb2685675aab3bbd1a3891bc@instructure.com>
Product-Review: Zach Pendleton <514dbb4d54d4f29e454990df7d075678f43f92aa@instructure.com>

--- app/coffeescripts/views/conversations/AutocompleteView.coffee
+++ app/coffeescripts/views/conversations/AutocompleteView.coffee
@@ -329,6 +329,7 @@
     # Returns nothing.
     _drawResults: ->
       @resultView.empty = !@resultView.collection.length
+      @resultView.$('.collectionViewItems').empty()
       @resultView.render()
       $el = @$resultList.find('li:first').addClass('selected')
       @selectedModel = @_getModel($el.data('id'))


better apply checking

better apply checking

--- src/facet/facet.coffee
+++ src/facet/facet.coffee
@@ -58,13 +58,13 @@
       if op.operation not in allow
         throw new Error("#{self} can not follow #{op.operation} (has to follow #{follow.join(', ')})")
       i--
-    if null not in follow
+    if '$start' not in follow
       throw new Error("#{self} can not be an initial command (has to follow #{follow.join(', ')})")
     return

   filter: (filter) ->
     @_ensureCommandOrder('filter'
-      [null]
+      ['$start']
       ['transform']
     )
     filter = _.clone(filter)
@@ -74,7 +74,7 @@

   split: (name, split) ->
     @_ensureCommandOrder('split'
-      [null, 'filter', 'split', 'apply', 'combine']
+      ['$start', 'filter', 'split', 'apply', 'combine']
       ['layout', 'scale', 'domain', 'range', 'transform', 'untransform', 'plot', 'connector', 'connect']
     )
     split = _.clone(split)
@@ -87,7 +87,8 @@

   apply: (name, apply) ->
     @_ensureCommandOrder('apply'
-      [null, 'split', 'apply']
+      ['$start', 'split', 'apply']
+      ['scale', 'domain', 'range', 'transform', 'untransform', 'plot']
     )
     apply = _.clone(apply)
     apply.operation = 'apply'
@@ -155,7 +156,7 @@

   branch: (data) ->
     @_ensureCommandOrder('branch'
-      [null]
+      ['$start']
       ['domain']
     )
     # create a branch split segment


FIX: Ensure enrolment modal appears

FIX: Ensure enrolment modal appears

* Add missing dependency

--- src/app/units/unit-student-list/unit-student-list.coffee
+++ src/app/units/unit-student-list/unit-student-list.coffee
@@ -14,7 +14,7 @@
     unit: "=unit"
     unitLoaded: "=unitLoaded"
     fullscreen: '=?'
-  controller: ($scope, $rootScope, $modal, $state, Project, $filter, currentUser, alertService, unitService, taskService, projectService, gradeService, analyticsService) ->
+  controller: ($scope, $rootScope, $modal, $state, Project, $filter, currentUser, alertService, unitService, taskService, projectService, gradeService, analyticsService, UnitStudentEnrolmentModal) ->
     $scope.studentFilter = 'myStudents' # Mine by default

     $scope.grades = gradeService.grades


server default express favicon

server default express favicon

--- src/server/app.coffee
+++ src/server/app.coffee
@@ -23,6 +23,8 @@

   setupExpress: ->
     @express = express()
+
+    @express.use(express.favicon())

     @express.set("root",@contentRoot)


Improve UX, better Player navigation instructions.

Easier to do than a jump meter.

--- src/state-play.coffee
+++ src/state-play.coffee
@@ -152,7 +152,7 @@
     _addInStateMenu: ->
       @inStateMenu = new InStateMenu [
         ['Paused', { fontSize: fontLarge }]
-        ['Arrow keys to move', { fontSize: fontSmall }]
+        ['Hold arrow keys to accelerate', { fontSize: fontSmall }]
         ['Press 0, -, + for volume', { fontSize: fontSmall }]
         ['Press Q to quit', { fontSize: fontSmall }]
       ], @game,


Test for multiple queries.

Test for multiple queries.

See #7.

--- tests.coffee
+++ tests.coffee
@@ -1,5 +1,6 @@
 Users = new Meteor.Collection 'Users_meteor_related_tests'
 Posts = new Meteor.Collection 'Posts_meteor_related_tests'
+Addresses = new Meteor.Collection 'Addresses_meteor_related_tests'
 Fields = new Meteor.Collection 'Fields_meteor_related_tests'

 if Meteor.isServer
@@ -38,6 +39,27 @@
           posts: 1
     ,
       Fields.find userId
+
+  Meteor.publish 'users-posts-and-addresses', (userId) ->
+    @related (user) ->
+      Posts.find(
+        _id:
+          $in: user?.posts or []
+      )
+    ,
+      Users.find userId,
+        fields:
+          posts: 1
+
+    @related (user) ->
+      Addresses.find(
+        _id:
+          $in: user?.addresses or []
+      )
+    ,
+      Users.find userId,
+        fields:
+          addresses: 1

   Meteor.publish 'users-posts-count', (userId, countId) ->
     @related (user) ->
@@ -73,6 +95,8 @@
     # Initialize the database.
     Users.remove {}
     Posts.remove {}
+    Addresses.remove {}
+    Fields.remove {}

   setUpClient: ->
     @countsCollection ?= new Meteor.Collection 'Counts'
@@ -352,5 +376,94 @@
       ]
   ]

+  testClientMultiple: [
+    ->
+      @userId = Random.id()
+
+      @assertSubscribeSuccessful 'users-posts-and-addresses', @userId, @expect()
+  ,
+    ->
+      @assertEqual Posts.find().fetch(), []
+      @assertEqual Addresses.find().fetch(), []
+
+      @posts = []
+
+      for i in [0...10]
+        Posts.insert {}, @expect (error, id) =>
+          @assertFalse error, error?.toString?() or error
+          @assertTrue id
+          @posts.push id
+
+      @addresses = []
+
+      for i in [0...10]
+        Addresses.insert {}, @expect (error, id) =>
+          @assertFalse error, error?.toString?() or error
+          @assertTrue id
+          @addresses.push id
+  ,
+    ->
+      @assertEqual Posts.find().fetch(), []
+      @assertEqual Addresses.find().fetch(), []
+
+      Users.insert
+        _id: @userId
+        posts: @posts
+        addresses: @addresses
+      ,
+        @expect (error, userId) =>
+          @assertFalse error, error?.toString?() or error
+          @assertTrue userId
+          @assertEqual userId, @userId
+  ,
+    ->
+      @assertItemsEqual _.pluck(Posts.find().fetch(), '_id'), @posts
+      @assertItemsEqual _.pluck(Addresses.find().fetch(), '_id'), @addresses
+
+      Users.update @userId,
+        $set:
+          posts: @posts[0..5]
+      ,
+        @expect (error, count) =>
+          @assertFalse error, error?.toString?() or error
+          @assertEqual count, 1
+  ,
+    ->
+      @assertItemsEqual _.pluck(Posts.find().fetch(), '_id'), @posts[0..5]
+      @assertItemsEqual _.pluck(Addresses.find().fetch(), '_id'), @addresses
+
+      Users.update @userId,
+        $set:
+          addresses: @addresses[0..5]
+      ,
+        @expect (error, count) =>
+          @assertFalse error, error?.toString?() or error
+          @assertEqual count, 1
+  ,
+    ->
+      @assertItemsEqual _.pluck(Posts.find().fetch(), '_id'), @posts[0..5]
+      @assertItemsEqual _.pluck(Addresses.find().fetch(), '_id'), @addresses[0..5]
+
+      Users.update @userId,
+        $unset:
+          addresses: ''
+      ,
+        @expect (error, count) =>
+          @assertFalse error, error?.toString?() or error
+          @assertEqual count, 1
+  ,
+    ->
+      @assertItemsEqual _.pluck(Posts.find().fetch(), '_id'), @posts[0..5]
+      @assertItemsEqual _.pluck(Addresses.find().fetch(), '_id'), []
+
+      Users.remove @userId, @expect (error, count) =>
+        @assertFalse error, error?.toString?() or error
+        @assertEqual count, 1
+  ,
+    ->
+      @assertItemsEqual _.pluck(Posts.find().fetch(), '_id'), []
+      @assertItemsEqual _.pluck(Addresses.find().fetch(), '_id'), []
+  ]
+
 # Register the test case.
 ClassyTestCase.addTest new RelatedTestCase()