Fix bug with the precedence of smart underline styles for same-color links within nested link containers

--- coffee/smart-underline.coffee
+++ coffee/smart-underline.coffee
@@ -9,7 +9,7 @@
 linkColorAttrName = 'data-smart-underline-link-color'
 linkSmallAttrName = 'data-smart-underline-link-small'
 linkLargeAttrName = 'data-smart-underline-link-large'
-linkContainerIdAttrName = 'data-smart-underline-container-id'
+containerIdAttrName = 'data-smart-underline-container-id'

 performanceTimes = []
 time = -> + new Date
@@ -87,13 +87,40 @@

 styleNode = document.createElement 'style'

+countParentContainers = (node, count = 0) ->
+  parentNode = node.parentNode
+  reachedRootNode = not parentNode or parentNode is document or parentNode is node
+
+  if reachedRootNode
+    return count
+
+  else
+    if parentNode.hasAttribute containerIdAttrName
+      count += 1
+
+    return count + countParentContainers(parentNode)
+
+sortContainersForCSSPrecendence = (containers) ->
+  sorted = []
+
+  for id, container of containers
+    container.depth = countParentContainers container.container
+    sorted.push container
+
+  sorted.sort (a, b) ->
+    return -1 if a.depth < b.depth
+    return 1 if a.depth > b.depth
+    return 0
+
+  return sorted
+
 init = (options) ->
   startTime = time()

   links = document.querySelectorAll "#{ if options.location then options.location + ' ' else '' }a"
   return unless links.length

-  linkContainers = {}
+  containers = {}
   for link in links
     style = getComputedStyle link
     fontSize = parseFloat style.fontSize
@@ -109,27 +136,30 @@
         if fontSize >= 20
           link.setAttribute linkLargeAttrName, ''

-        id = container.getAttribute linkContainerIdAttrName
+        id = container.getAttribute containerIdAttrName

         if id
-          linkContainers[id].links.push link
+          containers[id].links.push link
         else
           id = uniqueLinkContainerID()
-          container.setAttribute linkContainerIdAttrName, id
-          linkContainers[id] =
+          container.setAttribute containerIdAttrName, id
+          containers[id] =
+            id: id
             container: container
             links: [link]

   styles = ''

-  for id, container of linkContainers
+  containersWithPrecedence = sortContainersForCSSPrecendence containers
+
+  for container in containersWithPrecedence
     linkColors = {}
     linkColors[getLinkColor link] = true for link in container.links

     backgroundColor = getBackgroundColor container.container

     for color of linkColors
-      linkSelector = """[#{ linkContainerIdAttrName }="#{ id }"] a[#{ linkColorAttrName }="#{ color }"]"""
+      linkSelector = """[#{ containerIdAttrName }="#{ container.id }"] a[#{ linkColorAttrName }="#{ color }"]"""
       linkSmallSelector = """#{ linkSelector }[#{ linkSmallAttrName }]"""
       linkLargeSelector = """#{ linkSelector }[#{ linkLargeAttrName }]"""

@@ -178,7 +208,7 @@
 destroy = ->
   styleNode.parentNode?.removeChild styleNode

-  for attribute in [linkColorAttrName, linkSmallAttrName, linkLargeAttrName, linkContainerIdAttrName]
+  for attribute in [linkColorAttrName, linkSmallAttrName, linkLargeAttrName, containerIdAttrName]
     Array::forEach.call document.querySelectorAll("[#{ attribute }]"), (node) ->
       node.removeAttribute attribute


index: cleanup unnecessary dependencies

index: cleanup unnecessary dependencies

--- coffee/src/views/index.coffee
+++ coffee/src/views/index.coffee
@@ -21,7 +21,6 @@
 `import "shell_editor_view"`
 `import "shell_options_view"`
 `import "shell_selector_view"`
-`import "slider_handle_view"`
 `import "sliding_bar_view"`
 `import "sliding_object_view"`
 `import "sources_view"`


model.spec: run test remotely

model.spec: run test remotely

--- coffee/test/model.spec.coffee
+++ coffee/test/model.spec.coffee
@@ -91,7 +91,8 @@
     #TODO find a way to mock the server in the future.

     # Uncomment this to run locally:
-    acorn.config.setDomain 'localhost.athena.ai:8000'
+    # acorn.config.setDomain 'localhost.athena.ai:8000'
+
     acornid = undefined
     nyfskeqlyx = 'https://www.youtube.com/watch?v=yYAw79386WI'


corrections on cards-de.coffee

corrections on cards-de.coffee

--- coffeescripts/cards-de.coffee
+++ coffeescripts/cards-de.coffee
@@ -131,7 +131,7 @@
         '"Mauler Mithel"':
             text: """Wirf 1 zusätzlichen Angriffswürfel, wenn du ein Ziel in Reichweite 1 angreifst."""
         '"Howlrunner"':
-            name: '·"Kreischläufer"'
+            name: '"Kreischläufer"'
             text: """Wenn ein anderes freundliches Schiff in Reichweite 1 mit seinen Primärwaffen angreift, darf es 1 Angriffswürfel neu würfeln."""
         "Maarek Stele":
             text: """Wenn ein Verteidiger durch deinen Angriff eine offene Schadenskarte erhalten würde, ziehst du stattdessen 3 Schadenskarten, wählst eine davon zum Austeilen und legst die restlichen ab."""
@@ -516,23 +516,23 @@
         "R5-P9":
             text: """Am Ende der Kampfphase kannst du 1 deiner Fokusmarker ausgeben, um 1 Schild wiederaufzuladen (bis maximal zum Schildwert)."""
         "WED-15 Repair Droid":
-			name: "WED-15 Reparaturdroide"
+            name: "WED-15 Reparaturdroide"
             text: """Aktion: gib 1 Energie aus, um 1 deiner verdeckten Schadenskarten abzulegen oder gib 3 Energie aus, um 1 deiner offenen Schadenskarten abzulegen."""
         "Carlist Rieekan":
             text: """zu Beginn der Aktivierungsphase kannst du diese Karte ablegen, damit bis zum Ende der Phase der Pilotenwert aller freundlichen Schiffe 12 beträgt."""
         "Jan Dodonna":
             text: """Wenn ein anderes freundliches Schiff in Reichweite 1 angreift, darf es 1 seiner gewürfelten %HIT% in ein %CRIT% ändern."""
         "Expanded Cargo Hold":
-			name: "Erweiterter Ladebereich"
+            name: "Erweiterter Ladebereich"
             text: """Ein Mal pro Runde darfst du, wenn du eine offene Schadenskarte erhältst, frei wählen, ob du sie vom Schadensstapel Bug oder Heck ziehen willst."""
         "Backup Shield Generator":
-			name: "Sekundärer Schildgenerator"
+            name: "Sekundärer Schildgenerator"
             text: """Am Ende jeder Runde kannst du 1 Energie ausgeben, um 1 Schild wiederaufzuladen (bis maximal zum Schildwert)."""
         "EM Emitter":
-			name: "EM-Emitter"
+            name: "EM-Emitter"
             text: """Wenn du bei einem Angriff die Schussbahn versperrst, bekommst der Verteidiger 3 zusätzliche Verteidigungswürfel (anstatt 1)."""
         "Frequency Jammer":
-			name: "Störsender"
+            name: "Störsender"
             text: """Wenn du die Aktion Störsignal durchführst, wähle 1 feindliches Schiff ohne Stressmarker in Reichweite 1 des vom Störsignal betroffenen Schiffs. Das gewählte Schiff erhält 1 Stressmarker."""
         "Han Solo":
             text: """When attacking, if you have a target lock on the defender, you may spend that target lock to change all of your %FOCUS% results to %HIT% results."""
@@ -576,7 +576,7 @@
             name: "Advanced Cloaking Device"
             text: """After you perform an attack, you may perform a free cloak action."""
         "Combat Retrofit":
-			name: "Umrüstung für den Kampfeinsatz"
+            name: "Umrüstung für den Kampfeinsatz"
             text: """Erhöhe deinen Hüllenwert um 2 und deinen Schildwert um 1."""


more patterns

more patterns

--- flight-app.coffee
+++ flight-app.coffee
@@ -429,9 +429,12 @@
   --Lightest SS
   at 10 10
 rle $2o2$2o3$38bo$2o36bo$bo8bobo9bo6b3o$2bo6b2o9bobo6bo8bo$20bo17bo
-  """
-
-
+  """,
+  """
+  --Fastest orthogonal
+  at 14 2
+  rle 2bo38bo$2bo12bo5bo3bo8bo8bo5bobo$bo12bo6bo2bo10bo7bo5bobo$11bo11b2o6b2o2bo$b3o8bo11b2o9bo6bo$15bo7bo17b3o$11b2obo$43bo
+  """
 ]

 patternBounds = (lst) ->


Really Stop test suite from crashing when this one fails.

--- test/server/functional/prepaid.spec.coffee
+++ test/server/functional/prepaid.spec.coffee
@@ -50,8 +50,7 @@
             url = getURL("/db/prepaid/#{prepaid.id}/redeemers")
             redeemer = { userID: otherUser.id }
             request.post {uri: url, json: redeemer }, (err, res, body) ->
-              expect(body.redeemers).toBeDefined()
-              expect(body.redeemers.length).toBe(1)
+              expect(body.redeemers?.length).toBe(1)
               expect(res.statusCode).toBe(200)
               prepaid = Prepaid.findById body._id, (err, prepaid) ->
                 expect(err).toBeNull()


Ensure entities are in project so it can be opened immediately

--- components/BlobToEntry.coffee
+++ components/BlobToEntry.coffee
@@ -1,5 +1,6 @@
 noflo = require 'noflo'
 uuid = require 'uuid'
+collections = require '../src/collections'

 handleGraph = (sha, content, entry, project, callback) ->
   # Start by loading the graph object
@@ -20,6 +21,7 @@
       entry.local.endTransaction sha
       # Ensure the graph is marked as not changed since SHA
       entry.local.properties.changed = false
+      collections.addToList project.graphs, entry.local
       callback null, entry.local
       return

@@ -28,6 +30,7 @@
     graph.properties.id = uuid.v4()
     graph.properties.environment = {} unless graph.properties.environment
     graph.properties.environment.type = project.type unless graph.properties.environment.type
+    collections.addToList project.graphs, graph
     callback null, graph
     do callback

@@ -36,6 +39,7 @@
     entry.local.code = content
     entry.local.sha = sha
     entry.local.changed = false
+    collections.addToList project.components, entry.local
     callback null, entry.local
     return
   newEntry =
@@ -46,6 +50,7 @@
     language: entry.remote.language
     sha: sha
     changed: false
+  collections.addToList project.components, newEntry
   callback null, newEntry

 handleSpec = (sha, content, entry, project, callback) ->
@@ -53,6 +58,7 @@
     entry.local.code = content
     entry.local.sha = sha
     entry.local.changed = false
+    collections.addToList project.specs, entry.local
     callback null, entry.local
     return
   newEntry =
@@ -64,6 +70,7 @@
     language: entry.remote.language
     sha: sha
     changed: false
+  collections.addToList project.specs, newEntry
   callback null, newEntry

 exports.getComponent = ->
@@ -137,7 +144,5 @@
       res = {}
       res[entity.type] = entity.entity
       output.send res
-    setTimeout ->
-      output.sendDone
-        project: operation.project
-    , 10
+    output.sendDone
+      project: operation.project


Allow /auth/status to use credentials (cookies)

--- src/api/settings.coffee
+++ src/api/settings.coffee
@@ -33,6 +33,7 @@
     'user.send.statusUpdate':
       url: 'auth/status.json'
       method: 'GET'
+      useCredentials: true
       failureEvent: 'user.recieve.loadFailure'
       baseUrl: process?.env?.BASE_URL
       completedEvent: 'user.receive.statusUpdate'


Compare run file timestamp against origin configuration file

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

--- config/generateRunFile.coffee
+++ config/generateRunFile.coffee
@@ -157,7 +157,7 @@

     function checkrunfile () {

-      if [ "#{options.projectRoot}/run" -ot "#{options.projectRoot}/config/main.dev.coffee" ]; then
+      if [ "#{options.projectRoot}/run" -ot "#{options.projectRoot}/config/main.#{options.configName}.coffee" ]; then
           echo your run file is older than your config file. doing ./configure.
           sleep 1
           ./configure


Fixed issue with non-existent URL in lastIDs causing error

--- src/js/content/post.js.coffee
+++ src/js/content/post.js.coffee
@@ -16,7 +16,7 @@
     isMarking = no
     if Settings.markUnread
       url       = window.location.pathname.split("/").pop()
-      lastID    = Settings.lastIDs[url][0]
+      lastID    = Settings.lastIDs[url]?[0]
       if lastID?
         isMarking = yes
       else


Überflüssigen Event-Handler entfernt.

Überflüssigen Event-Handler entfernt.

--- src/main/coffee/script.coffee
+++ src/main/coffee/script.coffee
@@ -120,7 +120,6 @@
     L.control.scale().addTo(map)
     retrieveNodes()
     retrieveWays()
-    $("#retrieveDirections").click -> retrieveDirections()
     $("#setNodeAsSource").click -> setNodeAsSource()
     $("#setNodeAsDestination").click -> setNodeAsDestination()
     $("#wayEditMaxSpeed, #waySaveMaxSpeed, #wayCancelEditMaxSpeed").click -> toggleWayEditMaxSpeedControls()


Remove unused requires

Remove unused requires

--- src/autocomplete.coffee
+++ src/autocomplete.coffee
@@ -1,8 +1,5 @@
 {$$} = require 'space-pen'
-$ = require 'jquery'
-_ = require 'underscore'
 Range = require 'range'
-Editor = require 'editor'
 SelectList = require 'select-list'

 module.exports =


Extend cancelToken api.

Extend cancelToken api.

--- src/cancel-token.coffee
+++ src/cancel-token.coffee
@@ -6,6 +6,7 @@
             @handlers = []
             @set 'active', false
             @set 'canceled', false
+            @set 'cancelable', true
             @local = false
         addHandler: (fn) ->
             @handlers.push fn
@@ -24,3 +25,5 @@
         complete: ->
             @set 'active', false
             @set 'complete', true
+        canceled: ->
+            @get 'canceled'


Don't include binding.gyp files

Don't include binding.gyp files

--- build/tasks/build-task.coffee
+++ build/tasks/build-task.coffee
@@ -84,6 +84,7 @@
     ignoredPaths.push "#{_.escapeRegExp(path.join('spellchecker', 'src') + path.sep)}.*\\.(cc|h)*"

     # Ignore build files
+    ignoredPaths.push "#{_.escapeRegExp(path.sep)}binding\\.gyp$"
     ignoredPaths.push "#{_.escapeRegExp(path.sep)}.*\\.target.mk$"
     ignoredPaths.push "#{_.escapeRegExp(path.sep)}linker\\.lock$"


add win32 exe renaming

add win32 exe renaming

--- build/tasks/build-task.coffee
+++ build/tasks/build-task.coffee
@@ -25,6 +25,10 @@
       cp(path.join(shellAppDir, 'Contents', 'MacOS', 'Electron'),
          path.join(shellAppDir, 'Contents', 'MacOS', 'Nylas'))
       rm path.join(shellAppDir, 'Contents', 'MacOS', 'Electron')
+    else if process.platform is 'win32'
+      cp 'electron', shellAppDir, filter: /default_app/
+      cp path.join(shellAppDir, 'electron.exe'), path.join(shellAppDir, 'nylas.exe')
+      rm path.join(shellAppDir, 'electron.exe')
     else
       cp 'electron', shellAppDir, filter: /default_app/
       cp path.join(shellAppDir, 'electron'), path.join(shellAppDir, 'nylas')


Fixed the log message when removing generated files.

--- src/plugins/Generator.coffee
+++ src/plugins/Generator.coffee
@@ -41,8 +41,8 @@
   # Remove files
   removeFiles: (files) ->
     for file in files
-      fs.unlink file, (err) ->
-        logging.info " * Removed #{sysPath.relative(process.cwd(), file)}" unless err
+      fs.unlinkSync file
+      logging.info " * Removed #{sysPath.relative(process.cwd(), file)}"

   # Retrieve the model attributes
   parseAttrs: (args) ->


add file streaming test, alter escape slashing stuff to simplified version

--- test/lib/test-kevas.coffee
+++ test/lib/test-kevas.coffee
@@ -2,8 +2,9 @@
 kevas    = require '../../lib'
 strung   = require 'strung'
 through  = require 'through2'
-
-describe 'test kv-stream', ->
+Path     = require 'fspath'
+
+describe 'test kevas', ->

   shouldReplaceKeyWithValue = 'should replace the key with its value'
   for test in [
@@ -70,25 +71,25 @@
       result:'some good value'
     }
     {
-      desc: 'with a single escape slash'
-      should:'should have no slash'
+      desc: 'with one escape slashes next to each other'
+      should:'should include a single slash'
       output:['some \\ value']
       values: key:'good'
-      result:'some  value'
+      result:'some \\ value'
     }
     {
       desc: 'with two escape slashes next to each other'
       should:'should include a single slash'
       output:['some \\\\ value']
       values: key:'good'
-      result:'some \\ value'
+      result:'some \\\\ value'
     }
     {
       desc: 'with escaped opening brace'
       should:'should include the brace without the slash'
-      output:['some \\{ value']
-      values: key:'good'
-      result:'some { value'
+      output:['some \\{{ value']
+      values: key:'good'
+      result:'some {{ value'
     }
     {
       desc: 'with an escape slash before first opening brace which voids key'
@@ -98,55 +99,28 @@
       result:'some {{key}} value'
     }
     {
-      desc: 'with an escape slash before second opening brace which voids key'
-      should:'should match input without key replacement'
-      output:['some {\\{key}} value']
-      values: key:'good'
-      result:'some {{key}} value'
-    }
-    {
-      desc: 'with an escape slash before first closing brace which makes it part of the key'
-      should:'should use brace as part of key'
-      output:['some {{key\\}}} value']
-      values: key:'good', 'key}':'better'
-      result:'some better value'
-    }
-    {
-      desc: 'with an escape slash before second opening brace which makes both braces part of the key'
-      output:['some {{key}\\}}} value']
-      values: key:'good', 'key}}':'better', 'key}':'wrong'
-      result:'some better value'
-    }
-    {
       desc: 'with two escape slashes before first opening brace which doesnt affect the key'
       output:['some \\\\{{key}} value']
       values: key:'good'
-      result:'some \\good value'
+      result:'some \\\\good value'
     }

     # # # # # # # # # # # # # # # # # # # # # # # #
       # now redo escape test with split output  #
     # # # # # # # # # # # # # # # # # # # # # # # #
     {
-      desc: 'with a single escape slash'
-      should:'should have no slash'
-      output:['some \\', ' value']
-      values: key:'good'
-      result:'some  value'
-    }
-    {
       desc: 'with two escape slashes next to each other'
       should:'should include a single slash'
       output:['some \\', '\\ value']
       values: key:'good'
-      result:'some \\ value'
+      result:'some \\\\ value'
     }
     {
       desc: 'with escaped opening brace'
       should:'should include the brace without the slash'
-      output:['some \\', '{ value']
-      values: key:'good'
-      result:'some { value'
+      output:['some \\', '{{ value']
+      values: key:'good'
+      result:'some {{ value'
     }
     {
       desc: 'with an escape slash before first opening brace which voids key'
@@ -156,37 +130,25 @@
       result:'some {{key}} value'
     }
     {
-      desc: 'with an escape slash before second opening brace which voids key'
+      desc: 'with an escape slash before first opening brace which voids key'
       should:'should match input without key replacement'
-      output:['some {\\', '{key}} value']
+      output:['some \\{', '{key}} value']
       values: key:'good'
       result:'some {{key}} value'
-    }
-    {
-      desc: 'with an escape slash before first closing brace which makes it part of the key'
-      should:'should use brace as part of key'
-      output:['some {{key\\', '}}} value']
-      values: key:'good', 'key}':'better'
-      result:'some better value'
-    }
-    {
-      desc: 'with an escape slash before second opening brace which makes both braces part of the key'
-      output:['some {{key}\\', '}}} value']
-      values: key:'good', 'key}}':'better', 'key}':'wrong'
-      result:'some better value'
     }
     {
       desc: 'with two escape slashes before first opening brace which doesnt affect the key'
       output:['some \\\\', '{{key}} value']
       values: key:'good'
-      result:'some \\good value'
-    }
+      result:'some \\\\good value'
+    }
+
     {
       desc: 'with lots of output chunks'
       should:'should still escape a brace and match proper key'
-      output:['some {', '{', 'key}', '\\', '}', '}', '} value']
-      values: key:'good', 'key}}':'better', 'key}':'wrong'
-      result:'some better value'
+      output:['some {', '{', 'key', '}', '} value']
+      values: key:'good'
+      result:'some good value'
     }

   ]
@@ -203,13 +165,13 @@

           # create a sink stream to collect all it receives into a string
           output = strung()
-          # when all writes are finished to it verify the results
+
+          # when all writes are finished then verify the results
           output.on 'finish', ->
             assert.equal output?.string, test.result
-            # tell Mocha the test is done
-            done()
-
-          # send any errors to Moch's callback
+            done() # tell Mocha the test is done
+
+          # send any errors to Mocha's callback
           input.on  'error', done
           stream.on 'error', done
           output.on 'error', done
@@ -233,4 +195,42 @@
           writeout()

 # TODO: set of tests using a file stream as input (use filed? or paths?)
+  describe 'with json file input stream', ->
+
+    it 'should convert unescaped keys to values', (done) ->
+
+      dir = new Path 'test/helpers'
+
+      file = dir.to 'kevas.json'
+
+      expected = JSON.parse dir.to('expected.kevas.json').read()
+
+      stream = kevas values:key:'good'
+
+      output = strung()
+
+      # when all writes are finished then verify the results
+      output.on 'finish', (error) ->
+        if error? then return done error
+        result = JSON.parse output.string
+        assert.deepEqual result, expected
+        done() # tell Mocha the test is done
+
+      # send any errors to Mocha's callback
+      stream.on 'error', done
+      output.on 'error', done
+
+      options = # send errors to `done`
+        reader:
+          events:
+            'error': done
+        writer:
+          events:
+            'error': done
+
+      # pipe our test input to our stream and then to our output collector.
+      file.pipe(stream, options).pipe(output)
+
+
+
 # TODO: set of tests using a file stream as output (use filed? or paths?)


As top bar never changes it doesn't need a drop down reload.

Signed-off-by: Philip Vieira <3ba3974e96044dcde818e6310ed799abe10671d4@vallin.se>

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -37,6 +37,8 @@
     $.event.trigger "application.load"
     $.event.trigger "#{controller}.load"
     $.event.trigger "#{action}.#{controller}.load"
+
+  $('.topbar').dropdown()

   $(document).bind 'application.load', =>
     $(".alert-message > .close").bind "click", (e) ->
@@ -45,7 +47,6 @@
     $(".alert-message.primary").hide().fadeIn(500).delay(8000).fadeOut 500, ->
       hide_alert_message @

-    $('.topbar').dropdown()
     $('[rel=twipsy]').twipsy()

   $(document).bind 'end.pjax', (a,b,c) ->


Fixes issue where firefox does not delete message after comment submit

Issue was **not** in the way comment was being submitted.
`app/assets/javascripts/awards_handler.coffee` was throwing an error.
This error was preventing any js from running after it.
Fixes #1473

--- app/assets/javascripts/awards_handler.coffee
+++ app/assets/javascripts/awards_handler.coffee
@@ -5,7 +5,7 @@
       event.preventDefault()
       $(".emoji-menu").show()

-    $("html").click ->
+    $("html").on 'click', (event) ->
       if !$(event.target).closest(".emoji-menu").length
         if $(".emoji-menu").is(":visible")
           $(".emoji-menu").hide()


merit order: updated colours

merit order: updated colours

--- app/assets/javascripts/d3/merit_order.coffee
+++ app/assets/javascripts/d3/merit_order.coffee
@@ -1,23 +1,23 @@
 D3.merit_order =
   data:
     [
-      {key: 'central_gas_chp'},
+      {key: 'central_gas_chp', color: '#d9d9d9'},
       # {key: 'co_firing_coal'},
-      {key: 'coal_chp'},
-      {key: 'coal_conv'},
-      {key: 'coal_igcc'},
-      {key: 'coal_igcc_ccs'},
-      {key: 'coal_pwd'},
-      {key: 'coal_pwd_ccs'},
-      {key: 'gas_ccgt'},
-      {key: 'gas_ccgt_ccs'},
-      {key: 'gas_conv'},
-      {key: 'gas_turbine'},
-      {key: 'must_run'}, # something's broken here
-      {key: 'nuclear_iii'},
-      {key: 'oil_plant'},
-      {key: 'solar_pv'},
-      {key: 'wind_turbines'},
+      {key: 'coal_chp',        color: '#737373'},
+      {key: 'coal_conv',       color: '#000000'},
+      {key: 'coal_igcc',       color: '#525252'},
+      {key: 'coal_igcc_ccs',   color: '#9ebcda'},
+      {key: 'coal_pwd',        color: '#252525'},
+      {key: 'coal_pwd_ccs',    color: '#8c96c6'},
+      {key: 'gas_ccgt',        color: '#f0f0f0'},
+      {key: 'gas_ccgt_ccs',    color: '#bfd3e6'},
+      {key: 'gas_conv',        color: '#969696'},
+      {key: 'gas_turbine',     color: '#bdbdbd'},
+      {key: 'must_run',        color: '#a1d99b'},
+      {key: 'nuclear_iii',     color: '#fd8d3c'},
+      {key: 'oil_plant',       color: '#7f2704'},
+      {key: 'solar_pv',        color: '#fed976'},
+      {key: 'wind_turbines',   color: '#4292c6'}
     ]

   Node: class extends Backbone.Model
@@ -49,7 +49,6 @@
       # method raises (on IE8) an exception before we have a chance to notify
       # the user that something went wrong.
       try
-        @color = d3.scale.category20c()
         D3.merit_order.series = @model.series
         @nodes = new D3.merit_order.NodeList(D3.merit_order.data)
       @initialize_defaults()
@@ -94,7 +93,6 @@
               attr("text-anchor", "middle").
               attr("class", "axis_label")

-
       # nodes
       @svg.selectAll('rect.merit_order_node').
         data(@nodes.models, (d) -> d.get('key')).
@@ -102,20 +100,19 @@
         append("svg:rect").
         attr("data-rel", (d) -> d.get('key')).
         attr("class", "merit_order_node").
-        attr("fill", (d, i) => @color(i)).
-        attr("data-original-color", (d, i) => @color(i)).
-        style("stroke", (d, i) => d3.rgb(@color(i)).darker(1)).
+        attr("fill", (d) => d.get 'color').
+        style("stroke", (d) => d3.rgb(d.get 'color').darker(1)).
         attr("y", (d) => @height - @y(d.value_y()) - .5).
         attr("x", (d, i) -> i * 30).
         attr("height", (d) => @y d.value_y()).
         attr("width", (d) => @x d.value_x()).
         on("mouseover", ->
           item = d3.select(this)
-          item.transition().attr("fill", -> d3.rgb(item.attr("data-original-color")).brighter(1))
+          item.transition().attr("fill", (d) -> d3.rgb(d.get("color")).brighter(1))
         ).
         on("mouseout", ->
           item = d3.select(this)
-          item.transition().attr("fill", -> item.attr("data-original-color"))
+          item.transition().attr("fill", (d) -> d.get("color"))
         )

       $('rect.merit_order_node').qtip
@@ -126,7 +123,7 @@
           event: 'mouseout'  # silly IE
         position:
           my: 'bottom right'
-          at: 'top right'
+          at: 'top center'

       # add legend
       legends = @svg.selectAll("svg.legend").
@@ -142,7 +139,7 @@
       legends.append("svg:rect").
         attr("width", 10).
         attr("height", 10).
-        attr("fill", (d, i) => @color(i))
+        attr("fill", (d) => d.get 'color')
       legends.append("svg:text").
         text((d) -> I18n.t "output_element_series.#{d.get('key')}").
         attr("x", 15).


fixed multiple search results showing up

--- app/assets/javascripts/main/paginator.coffee
+++ app/assets/javascripts/main/paginator.coffee
@@ -57,6 +57,7 @@

     @dataRetrievalPromise = $.ajax(ajaxOptions)
     @dataRetrievalPromise.then(@handleData)
+

   handleData: (responseData) =>

@@ -141,7 +142,7 @@
     for element in jsonArray
       htmlArray.push(@generateHTML(element))

-    @tbody.append(htmlArray.join(""))
+    @tbody.html(htmlArray.join(""))

   JSONcontains : (data, query) ->


Feeling out the new cool CSmap thingie,.

--- app/assets/javascripts/roofpig/Alg.js.coffee
+++ app/assets/javascripts/roofpig/Alg.js.coffee
@@ -17,7 +17,7 @@

   @_make_action: (code) ->
     if code.indexOf('+') > -1
-      moves = (code.split('+').map (code) -> Alg._make_action(code))
+      moves = (Alg._make_action(code) for code in code.split('+'))
       new CompositeMove(moves)
     else
       if code.indexOf('>') > -1 || code.indexOf('<') > -1


Only Navigate away if the HREF location has been set.

This prevents delayed navigation if the a# that was clicked didn’t have a
href url

[#63474246]

https://www.pivotaltracker.com/story/show/63474246

--- app/assets/javascripts/save-on-change.coffee
+++ app/assets/javascripts/save-on-change.coffee
@@ -61,7 +61,7 @@

   intercept_navigation: ->
     $("a").on 'click', (e) =>
-      @stored_location = e.target.href
+      @stored_location = e.currentTarget.href
       @force_save_dirty()
       e.preventDefault()

@@ -79,7 +79,8 @@
     @dirty_forms[form] = form;

   navigate_away: ->
-    window.location = @stored_location
+    if @stored_location
+      window.location = @stored_location

   mark_clean: (form) ->
     delete @dirty_forms[form]


refactor of coffee, fixing broken behaviors

--- app/assets/javascripts/settlements.js.coffee
+++ app/assets/javascripts/settlements.js.coffee
@@ -4,59 +4,59 @@

 $ = jQuery
+
 debug = false

-class Settlement
-  constructor: ->
-    @name = $('#settlement_name')
-    @pop  = $('#settlement_population')
-    this.initRandcheck()
-    @pop.keyup => this.forceInt()
-    #make the selection box a pretty pretty princess
-    $('select').selectbox()
-    @pop.keyup => this.updateDrop()
-    $('#create-settlement').click -> this.createCallback
-    $('.selectbox-wrapper').on 'click', =>
-      this.sizeSettlement()
-    $('li.export').click ->
-      $(this).find($('ul.export-menu')).slideToggle(200)
+$ ->
+  #
+  # set up the colorpicker
+  #

-  initRandcheck: ->
-    $('.randcheck.settlement-name').click =>
-      $.get '/random/name/settlement',
-        {}
-        (data) => @name.val(data)
-        'html'
-    $('.submit-form').click ->
-        $(this).parent('form').
-        eq(0).
-        trigger 'submit'
-      false
-
-  sizeSettlement: () ->
-    @pop.val $('#size').val()
-
-  # change the dropbox to reflect the value in the pop field
-  updateDrop: () =>
-    pop = parseInt(@pop.val())
-    $.each $('#size option'), ->
-      if pop > parseInt($(this).val())
-          $('#size_input').val($(this).text())
-
-  # force the entry in the population to be an int
-  forceInt: () =>
-    parsed = parseInt(@pop.val())
-    if (isNaN(parsed))
-       @pop.val('')
-    else
-       @pop.val(parseInt(@pop.val()))
+  $('#settlement-color').colorpicker().blur ->
+    $('#icon-preview').css('background-color', $(this).val())

+  #
+  # icon typeahead
+  #
+  $('#settlement-icon').typeahead
+    source: $.getJSON '/icon-list'
+    items: 5

-$(document).ready ->
-  settlement = new Settlement();
+  #
+  # random icon
+  #
+  $('#random-icon').click (e) =>
+    $.getJSON '/icon-list', (data) ->
+      $('#settlement-icon').val(data[Math.floor(Math.random()*data.length)])
+      preview()

+  #
+  # preview for the icon
+  #
+  preview = ->
+    $('#icon-preview').css('background-image', "url('/assets/heraldry/" + $('#settlement-icon').val() + ".png')")

+  $('#settlement-icon').change preview

+  #
+  # random settlement name
+  #
+  $('#random-name').click =>
+    $.get '/random/name/settlement',
+        {}
+        (data) => $('#settlement-name').val(data)
+        'html'

+  #
+  # force the entry in the population box to be an integer
+  #
+  forceInt = ->
+    pop = $('#settlement-population')
+    parsed = parseInt(pop.val())
+    if (isNaN(parsed))
+       pop.val('')
+    else
+       pop.val(parsed)

+  $('#settlement-population').keyup forceInt


cleaner bug fix

cleaner bug fix

--- app/assets/javascripts/validations.js.coffee
+++ app/assets/javascripts/validations.js.coffee
@@ -6,8 +6,7 @@
     if(remaining >= 0)
       $(".character-counter").text(pluralize_characters(remaining) + " left")
     else
-      num = num * (-1)
-      $(".character-counter").text(pluralize_characters(remaining) + " too long")
+      $(".character-counter").text(pluralize_characters(-remaining) + " too long")

 pluralize_characters = (num) ->
   if(num == 1)


remove multiline override

remove multiline override

--- app/assets/javascripts/zvg/multi_line.coffee
+++ app/assets/javascripts/zvg/multi_line.coffee
@@ -1,8 +1,6 @@
 class ZVG.MultiLine extends ZVG.MultiPoint
   # Don't render lines if we have a filter active.
   _should_render_lines: ->
-    if (@_data.length is 1) # FIXME for series
-      return false
     @_series_3_domain.length is 1 and @_series_3_domain[0] is "all"

   debug: true


document.head exists now; the future!

document.head exists now; the future!

--- resources/assets/coffee/_classes/store-checkout.coffee
+++ resources/assets/coffee/_classes/store-checkout.coffee
@@ -29,8 +29,7 @@
     script.addEventListener 'load', ->
       deferredScript.resolve()
     , false
-    head = document.getElementsByTagName('head')[0]
-    head.appendChild(script)
+    document.head.appendChild(script)

     deferredToken = $.Deferred()
     # get token


CHANGE: draw: padding: (...) now resets the draw-area, not adds-padding (previously repeated calls to padding continuted to increase padding with each one)

--- source/Art/Engine/Core/Drawing/ElementDrawMixin.coffee
+++ source/Art/Engine/Core/Drawing/ElementDrawMixin.coffee
@@ -251,7 +251,7 @@
                   when shape      then customShapePath

               if padding
-                currentDrawArea = padding.pad currentDrawArea
+                currentDrawArea = padding.pad @currentSize

               if clip?
                 if clip


Payment: group creation form simplified, removed change

--- client/Pricing/AppView.coffee
+++ client/Pricing/AppView.coffee
@@ -187,13 +187,14 @@
       callback              : -> @emit "Submit"
       buttons               :
         Create              :
-          title             : "CREATE"
+          title             : "CREATE YOUR GROUP"
           type              : "submit"
           style             : "solid green"
       fields                :
         GroupName           :
           label             : "Group Name"
           name              : "groupName"
+          placeholder       : "My Awesome Group"
           keyup             : =>
             @checkSlug @groupForm.inputs.GroupName.getValue()
           validate          :
@@ -202,20 +203,23 @@
             messages        :
               required      : "Group name required"
         GroupURL            :
-          label             : "Address"
-          defaultValue      : "#{window.location.origin}"
-          disabled          : yes
+          label             : "Group address"
+          defaultValue      : "#{window.location.origin}/"
+          # disabled          : yes
           keyup             : =>
             splittedUrl = @groupForm.inputs.GroupURL.getValue().split "/"
             @checkSlug splittedUrl.last
-          nextElement       :
-            changeURL       :
-              itemClass     : KDCustomHTMLView
-              tagName       : "a"
-              partial       : 'change'
-              click         : =>
-                @groupForm.inputs.GroupURL.makeEnabled()
-                @groupForm.inputs.GroupURL.focus()
+
+          # don't push it in if you can't do it right! - SY
+
+          # nextElement       :
+          #   changeURL       :
+          #     itemClass     : KDCustomHTMLView
+          #     tagName       : "a"
+          #     partial       : 'change'
+          #     click         : =>
+          #       @groupForm.inputs.GroupURL.makeEnabled()
+          #       @groupForm.inputs.GroupURL.focus()
         GroupSlug           :
           type              : "hidden"
           name              : "groupSlug"


Fix typo

Fix typo

--- client/admin/lib/index.coffee
+++ client/admin/lib/index.coffee
@@ -36,7 +36,7 @@
         { slug : 'General',        title : 'General',           viewClass : GroupGeneralSettingsView, role: 'member' }
         { slug : 'Members',        title : 'Members',           viewClass : AdminMembersView         }
         { slug : 'Invitations',    title : 'Invitations',       viewClass : AdminInvitationsView     }
-        { slug : 'Resources',      title : 'Rescources',        viewClass : AdminResourcesView       }
+        { slug : 'Resources',      title : 'Resources',         viewClass : AdminResourcesView       }
       # { slug : 'Permissions',    title : 'Permissions',       viewClass : GroupPermissionsView     }
         {
           slug      : 'Integrations'


add comment and better wrapping

add comment and better wrapping

--- apps/articles/client/gallery_insights.coffee
+++ apps/articles/client/gallery_insights.coffee
@@ -33,23 +33,28 @@

   setupCTAWaypoints: ->
     return if @ctaBarView.previouslyDismissed()
-    @ctaBarView.render()
-    MktoForms2?.whenReady (form) -> $('#mktoForm_1230').removeAttr 'id'
-    @ctaBarView.$el?.find('.cta-bar-small-form').replaceWith marketoForm()
-    @$el.append @ctaBarView.$el
-    if @inGIArticlePage()
-      @$(".article-container[data-id=#{sd.ARTICLE.id}]").waypoint (direction) =>
-        @ctaBarView.transitionIn() if direction is 'down'
-      , { offset: -200 }
-      @$(".article-container[data-id=#{sd.ARTICLE.id}]").waypoint (direction) =>
-        @ctaBarView.transitionOut() if direction is 'down'
-        @ctaBarView.transitionIn() if direction is 'up'
-      , { offset: 'bottom-in-view' }
-    else if @inGIVerticalPage()
-      @$('.js-articles-feed-articles').waypoint (direction) =>
-        @ctaBarView.transitionIn() if direction is 'down'
-      ,{ offset: '50%' }
-      @$('.js-articles-feed-articles').waypoint (direction) =>
-        @ctaBarView.transitionOut() if direction is 'down'
-        @ctaBarView.transitionIn() if direction is 'up'
-      , { offset: 'bottom-in-view' }
+    # We have to wait for the first Marketo form embedded at the bottom of
+    # the page to render and remove the Marketo id from the element. Otherwise
+    # Marketo will try to render the form in both places, causing the form to
+    # appear twice at the bottom of the page.
+    MktoForms2.whenReady _.once (form) =>
+      @$('#mktoForm_1230').removeAttr 'id'
+      @ctaBarView.render()
+      @ctaBarView.$el?.find('.cta-bar-small-form').replaceWith marketoForm()
+      @$el.append @ctaBarView.$el
+      if @inGIArticlePage()
+        @$(".article-container[data-id=#{sd.ARTICLE.id}]").waypoint (direction) =>
+          @ctaBarView.transitionIn() if direction is 'down'
+        , { offset: -200 }
+        @$(".article-container[data-id=#{sd.ARTICLE.id}]").waypoint (direction) =>
+          @ctaBarView.transitionOut() if direction is 'down'
+          @ctaBarView.transitionIn() if direction is 'up'
+        , { offset: 'bottom-in-view' }
+      else if @inGIVerticalPage()
+        @$('.js-articles-feed-articles').waypoint (direction) =>
+          @ctaBarView.transitionIn() if direction is 'down'
+        ,{ offset: '50%' }
+        @$('.js-articles-feed-articles').waypoint (direction) =>
+          @ctaBarView.transitionOut() if direction is 'down'
+          @ctaBarView.transitionIn() if direction is 'up'
+        , { offset: 'bottom-in-view' }


fix error

fix error

--- www/index.coffee
+++ www/index.coffee
@@ -26,7 +26,7 @@
             video   : @video.parse()
           response.page "base", bindings, ["partial.video"]
         else
-          site.redirect "/"
+          response.redirect "/"

     else


Update menu test to match new options

Update menu test to match new options

--- test/user/menu.spec.coffee
+++ test/user/menu.spec.coffee
@@ -26,4 +26,5 @@
     it 'renders modification when registered', ->
       sinon.stub(@props.course, 'isRegistered').returns(true)
       Testing.renderComponent( Menu, props: @props ).then ({dom}) ->
-        expect(dom.textContent).to.match(/Change Course and ID/)
+        expect(dom.textContent).to.match(/Change Course/)
+        expect(dom.textContent).to.match(/Change student ID/)


empty

empty

--- cf/spell.coffee
+++ cf/spell.coffee
@@ -55,6 +55,7 @@
         if attempt.toLowerCase() == @current.toLowerCase()
           $(".alert").addClass("alert-success")
           $(".alert").empty().append("You had just spelt the word " + @current + " right!")
+          $("#revealed").empty()
           update_question(@data)
           emptyInput()
         else


Update all.js.coffee

Update all.js.coffee

--- app/assets/javascripts/darkswarm/all.js.coffee
+++ app/assets/javascripts/darkswarm/all.js.coffee
@@ -37,6 +37,7 @@
 #= require moment/nb.js
 #= require moment/pt-br.js
 #= require moment/pt.js
+#= require moment/ru.js
 #= require moment/sv.js
 #
 #= require modernizr


[feature] better error management

[feature] better error management

--- app/controllers/applications_controller.coffee
+++ app/controllers/applications_controller.coffee
@@ -7,21 +7,48 @@

 # Helpers

-send_error = (msg, code=500) ->
-    if msg
-        send error: true, msg: msg, code
-    else
-        send error: true, msg: "Server error occured", code
+send_error = (err, code=500) ->
+
+
+    err ?=
+        stack:   null
+        message: "Server error occured"
+
+    console.log "Sending error to client :"
+    console.log err.stack
+
+    send
+        error: true
+        success: false
+        message: err.message
+        stack: err.stack
+    , code
+
+mark_broken = (app, err) ->
+    console.log "Marking app as broken because"
+    console.log err.stack
+
+    app.state = "broken"
+    app.save (saveErr) ->
+
+        return send_error saveErr if saveErr
+
+        send
+            app: app
+            error: true
+            success: false
+            message: err.message
+            stack: err.stack
+        , 500

 # Load application corresponding to slug given in params
 before 'load application', ->
     Application.all key: params.slug, (err, apps) =>
         if err
-            console.log err
-            send_error()
+            send_error err
         else if apps is null or apps.length == 0
-            send error: 'Application not found', 404
+            send_error new Error('Application not found'), 404
         else
             @app = apps[0]
             next()
@@ -30,17 +57,11 @@

 ## Actions

-
-# Home page of the application, render browser UI.
-action 'index', ->
-    layout false
-    render title: "Cozy Home"
-
 # Return list of applications available on this cozy instance.
 action 'applications', ->
     Application.all (errors, apps) ->
         if errors
-            send_error "Retrieve applications failed."
+            send_error new Error "Retrieve applications failed."
         else
             send rows: apps

@@ -51,55 +72,40 @@
 # * database
 # Send an error if an application already has same slug.
 action "install", ->
+
     body.slug = slugify body.name
     body.state = "installing"

-    setupApp = (appli) ->
-        manager = new AppManager
-
-        console.info 'attempt to install app ' + JSON.stringify(appli)
-        manager.installApp appli, (err, result) ->
-            if err
-                appli.state = "broken"
-                appli.save (saveErr) ->
-                    if saveErr
-                        send_error saveErr.message
-                    else
-                        send
-                            error: true
-                            success: false
-                            message: err.message
-                            app:app
-                            , 201
-            else
+    Application.all key: body.slug, (err, apps) ->
+
+        return send_error err, 500 if err
+
+        if apps.length > 0
+            err = new Error "There is already an app with similar name"
+            return send_error err, 400
+
+
+        Application.create body, (err, appli) ->
+
+            return send_error err if err
+
+            console.info 'attempt to install app ' + JSON.stringify(appli)
+            manager = new AppManager()
+            manager.installApp appli, (err, result) ->
+
+                return mark_broken appli, err if err
+
                 appli.state = "installed"
-                appli.port = result.drone.port
-                appli.save (saveErr) ->
-                    if saveErr
-                        send_error saveErr.message
-                    else
-                        manager.resetProxy (proxyErr) ->
-                            if proxyErr
-                                railway.logger.write "Proxy reset failed."
-                                send_error proxyErr.message
-                            else
-                                send { success: true, app: appli }, 201
-
-    Application.all key: body.slug, (err, apps) ->
-        if err
-            send_error err.message
-        else if apps.length
-            # TODO take care of request retry
-            # (happens when install is taking too long)
-            # may be send a 100 - Continue
-            send_error "There is already an app with similar name", 400
-        else
-            Application.create body, (err, app) ->
-                if err
-                    send_error err.message
-                else
-                    setupApp app
-
+                appli.port  = result.drone.port
+                appli.save (err) ->
+
+                    return send_error err if err
+
+                    manager.resetProxy (err) ->
+
+                        return send_error err if err
+
+                        send success: true, app: appli, 201

 # Remove app from 3 places :
 # * haibu, application manager
@@ -107,34 +113,22 @@
 # * database
 action "uninstall", ->

-    markAppAsBroken = =>
-        @app.state = "broken"
-        @app.save (err) ->
-            if err
-                send_error()
-            else
-                send_error "uninstallation failed"
-
-    removeAppFromDb = =>
+    manager = new AppManager
+    manager.uninstallApp @app, (err, result) =>
+
+        return mark_broken @app, err if err
+
         @app.destroy (err) ->
-            if err
-                console.log err
-                send_error 'Cannot destroy app'
-            else
-                manager.resetProxy (err) ->
-                    if err
-                        send_error 'Reset Proxy failed.'
-                    else
-                        send
-                            success: true
-                            msg: 'Application succesfuly uninstalled'
-
-    manager = new AppManager
-    manager.uninstallApp @app, (err, result) =>
-        if err
-            markAppAsBroken()
-        else
-            removeAppFromDb()
+
+            return send_error err if err
+
+            manager.resetProxy (err) ->
+
+                return send_error proxyErr if err
+
+                send
+                    success: true
+                    msg: 'Application succesfuly uninstalled'

 # Update an app :
 # * haibu, application manager
@@ -142,90 +136,65 @@
 # * database
 action "update", ->

-    markAppAsBroken = =>
-        @app.state = "broken"
+    manager = new AppManager
+    manager.updateApp @app, (err, result) =>
+
+        return mark_broken @app, err if err
+
+        @app.state = "installed"
+        @app.port = result.drone.port
         @app.save (err) ->
-            if err
-                send_error()
-            else
-                send_error "uninstallation failed"
-
-    manager = new AppManager
-    manager.updateApp @app, (err, result) =>
-        if err
-            markAppAsBroken()
-        else
-            @app.state = "installed"
-            @app.port = result.drone.port
-            @app.save (err) ->
-                if err
-                    send_error()
-                else
-                    manager.resetProxy (err) ->
-                        if err
-                            markAppAsBroken()
-                        else
-                            send
-                                success: true
-                                msg: 'Application succesfuly updated'
+
+            return send_error err if err
+
+            manager.resetProxy (err) ->
+
+                return mark_broken @app, err if err
+
+                send
+                    success: true
+                    msg: 'Application succesfuly updated'

 action "start", ->
-
-    markAppAsBroken = =>
-        @app.state = "broken"
-        @app.save (err) ->
-            if err
-                send_error err.message
-            else
-                send_error "starting failed"
-
-
     manager = new AppManager
     manager.start @app, (err, result) =>
-        if err
-            markAppAsBroken()
-        else
-            @app.state = "installed"
-            @app.port = result.drone.port
-            @app.save (err) =>
-                if err
-                    markAppAsBroken()
-                else
-                    manager.resetProxy (err) =>
-                        if err
-                            markAppAsBroken()
-                        else
-                            send
-                                success:true
-                                msg: 'Application running'
-                                app: @app
+
+        return mark_broken @app, err if err
+
+        # require('eyes').inspect result
+
+        @app.state = "installed"
+        @app.port = result.drone.port
+        @app.save (err) =>
+
+            return send_error err if err
+
+            manager.resetProxy (err) =>
+
+                return mark_broken @app, err if err
+
+                send
+                    success: true
+                    msg: 'Application running'
+                    app: @app

 action "stop", ->
-
-    markAppAsBroken = =>
-        @app.state = "broken"
-        @app.save (err) ->
-            if err
-                send_error()
-            else
-                send_error "stopping failed"
-
     manager = new AppManager
     manager.stop @app, (err, result) =>
-        if err
-            markAppAsBroken()
-        else
-            @app.state = "stopped"
-            @app.port = 0
-            @app.save (err) =>
-                if err
-                    send_error()
-                else
-                    manager.resetProxy (err) =>
-                        if err
-                            markAppAsBroken()
-                        else
-                            send
-                                success:true
-                                msg: 'Application stopped'
-                                app: @app
+
+        return mark_broken @app, err if err
+
+        @app.state = "stopped"
+        @app.port = 0
+        @app.save (err) =>
+
+            return send_error err if err
+
+            manager.resetProxy (err) =>
+
+                return mark_broken @app, err if err
+
+                send
+                    success:true
+                    msg: 'Application stopped'
+                    app: @app


remove compeition details from the home page

--- overrides.coffee
+++ overrides.coffee
@@ -6,7 +6,6 @@
 require './attribute-background-image'
 require './confirm-button-label'
 require './task-defaults'
-require './competition'

 GoogleAnalytics = require 'zooniverse/lib/google-analytics'
 analytics = new GoogleAnalytics


Update cards-es.coffee

Update cards-es.coffee

New Wave 5 cards have been translated.

--- coffeescripts/cards-es.coffee
+++ coffeescripts/cards-es.coffee
@@ -410,13 +410,13 @@
             ship: "Ala-A"
         "Gemmer Sojan":
             name: "Gemmer Sojan"
-            text: """While you are at Range 1 of at least 1 enemy ship, increase your agility value by 1."""
+            text: """Mientras permanezcas a alcance 1 de al menos 1 nave enemiga, incrementa tu valor de agilidad en 1."""
             ship: "Ala-A"
         "Keyan Farlander":
             text: """Cuando ataques, puedes retirar una ficha de tensión  para cambiar todos tus resultados %FOCUS% por %HIT%."""
             ship: "Ala-B"
         "Nera Dantels":
-            text: """You can perform %TORPEDO% secondary weapon attacks against enemy ships outside your firing arc."""
+            text: """Puedes ralizar un ataque con armamento secundario %TORPEDO% contra enemigos fuera de tu arco de fuego."""
             ship: "Ala-B"
         "CR90 Corvette (Crippled Aft)":
             name: "CR90 Corvette (Crippled Aft)"
@@ -428,15 +428,15 @@
         "Dash Rendar":
             text: """Puedes ignorar obstáculos durante la fase de activación y cuando ejecutes acciones."""
         '"Leebo"':
-            text: """When you are dealt a faceup Damage card, draw 1 additional Damage card, choose 1 to resolve, and discard the other."""
+            text: """Cuando recibas una carta de daño boca arriba, puedes robar 1 carta de daño adicional, elige 1 de ellas y resuelvela, descarta la otra."""
         "Eaden Vrill":
-            text: """When performing a primary weapon attack against a stressed ship, roll 1 additional attack die."""
+            text: """Cuando realices un ataque con tu armamento principal contra una nave con al menos un marcador de Tensión, puedes tirar 1 dado de ataque adicional."""
         "Rear Admiral Chiraneau":
             text: """Cuando ataques a distancia 1-2, puedes cambiar 1 de tus resultados de %FOCUS% a %CRIT%."""
         "Commander Kenkirk":
-            text: """If you have no shields and at least 1 Damage card assigned to you, increase your agility value by 1."""
+            text: """Si no tienes escudos y al menos una carta de daño asignada a tu nave, incrementa tu valor de agilidad en 1."""
         "Captain Oicunn":
-            text: """After executing a maneuver, each enemy ship you are touching suffers 1 damage."""
+            text: """Después de ejecutar un maniobra, cada nave enemiga que estés tocando sufre 1 daño."""

     upgrade_translations =
         "Ion Cannon Turret":
@@ -652,7 +652,7 @@
             text: """Esta carta tiene un valor negativo en puntos de escuadrón."""
         "Proton Rockets":
             name: "Cohetes de Protones"
-            text: """Attack (Focus): Discard this card to perform this attack.

You may roll additional attack dice equal to your agility value, to a maximum of 3 additional dice."""
+            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."""
         "Kyle Katarn":
             text: """Despues de retires una ficha de Tensión de tu nave, puedes asiganar 1 ficha de %FOCUS% a tu nave."""
         "Jan Ors":
@@ -708,7 +708,7 @@
             text: """Durante la fase de activación, si enseñas una maniobra %STRAIGHT%, recibes 1 de Energía adicional durante el paso de "Obtener Energía"."""
         "Lando Calrissian":
             name: "Lando Calrissian"
-            text: """Acción: TIra 2 dados ded defensa.  Por cada resultado de %FOCUS%, asigna 1 dicha de Concentración a tu nave. Por cada resultado de %EVADE%, asigna 1 ficha de Evasión a tu nave."""
+            text: """Acción: TIra 2 dados ded defensa. Por cada resultado de %FOCUS%, asigna 1 dicha de Concentración a tu nave. Por cada resultado de %EVADE%, asigna 1 ficha de Evasión a tu nave."""
         "Mara Jade":
             name: "Mara Jade"
             text: """Al final de la fase de Combate, cada nave enemiga a alcance 1 que no tenga fichas de Tensión recive 1 ficha de Tensión."""
@@ -716,23 +716,23 @@
             name: "Oficial de Flota"
             text: """Acción: Elige hasta 2 naves aliadas a alcance 1-2 y asigna 1 ficha de Concentración a cada una de ellas. Después recibes una ficha de Tensión."""
         "Lone Wolf":
-            text: """When attacking or defending, if there are no other friendly ships at Range 1-2, you may reroll 1 of your blank results."""
+            text: """Cuando ataques o defiendas, si no hay ninguna nave aliada a alcance 1-2, pues retirar 1 de tus resultados de cara vacía."""
         "Stay On Target":
-            text: """When you reveal a maneuver, you may rotate your dial to another maneuver with the same speed.

Treat that maneuver as a red maneuver."""
+            text: """Cuando reveles tu dial de maniobra, puedes cambiarlo y seleccionar una maniobra diferente con la misma velocidad.

Trata la nueva maniobra seleccionada como si fuera roja."""
         "Dash Rendar":
-            text: """You may perform attacks while overlapping an obstacle.

Your attacks cannot be obstructed."""
+            text: """Puedes realizar ataques mientras te solapas con un obstáculo.

Tus ataques no pueden ser obstruidos."""
         '"Leebo"':
-            text: """Action: Perform a free boost action.  Then receive 1 ion token."""
+            text: """Acción: Realiza una acción gratuita de Impulso. Recibe 1 marcador de iones."""
         "Ruthlessness":
-            text: """After you perform an attack that hits, you must choose 1 other ship at Range 1 of the defender (other than yourself).  That ship suffers 1 damage."""
+            text: """Después de que realices un ataque que impacte, tu debes elegir 1 nave a alcance 1 del defensor (a parte de ti).  Esa nave sufre 1 daño."""
         "Intimidation":
-            text: """While you are touching an enemy ship, reduce that ship's agility value by 1."""
+            text: """Mientras estes en contacto con una nave enemiga, reduce su agilidad en 1."""
         "Ysanne Isard":
-            text: """At the start of the Combat phase, if you have no shields and at least 1 Damage card assigned to your ship, you may perform a free evade action."""
+            text: """Al comienzo de la fase de combate, si no tienes escudos y al menos tienes 1 carta dedaño asignada a tu nave, puedes realizar una acción gratuita de Evadir."""
         "Moff Jerjerrod":
-            text: """When you are dealt a faceup Damage card, you may discard this Upgrade card or another %CREW% Upgrade card to flip that Damage card facedown (without resolving its effect)."""
+            text: """Cuando se te asigna un carta de daño boca arribar, puedes descartar esta mejora u otra de %CREW% para poner boca abajo esa carta de daño (sin resolver su efecto)."""
         "Ion Torpedoes":
-            text: """Attack (target lock): Spend your target lock and discard this card to perform this attack.

If this attack hits, the defender and each ship at Range 1 of it receives 1 ion token."""
+            text: """Ataque (Blanco Fijado): Retira el blanco fijado y descarta esta carta para realizar este ataque.

Si el ataque impacta, el defensor y cada nave a alcance 1 de él recibe 1 marcador de iones."""

     modification_translations =
         "Stealth Device":
@@ -755,7 +755,7 @@
             text: """Incrementa tu valor de blindaje en 1."""
         "Munitions Failsafe":
             name: "Sistema de Munición a Prueba de Fallas"
-            text: """Cuando ataques on un arma secundaria que te instruye que la descartes para realizar un ataque, no la descartes a menos de que el defensor sea alcanzado por tu ataque."""
+            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."""
@@ -771,11 +771,11 @@
             text: """Tu barra de mejoras gana %CREW%."""
             ship: "Ala-B"
         "Countermeasures":
-            text: """At the start of the Combat phase, you may discard this card to increase your agility value by 1 until the end of the round.  Then you may remove 1 enemy target lock from your ship."""
+            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."""
         "Experimental Interface":
-            text: """Once per round, after you perform an action, you may perform 1 free action from an equipped Upgrade card with the "Action:" header.  Then receive 1 stress token."""
+            text: """Una vez por ronda, trás realizar una acción, puedes hacer una acción gratuita de alguna de tus mejoras equipadas que tenga el texto "Acción:".  Recibe una ficha de tension."""
         "Tactical Jammer":
-            text: """Your ship can obstruct enemy attacks."""
+            text: """Tu nave obstruye ataques enemigos."""

     title_translations =
         "Slave I":
@@ -811,7 +811,7 @@
             text: """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"
+            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."""
             ship: 'Transporte mediano GR-75'
         "Dutyfree":
@@ -825,6 +825,6 @@
             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."""
         "Dauntless":
-            text: """After you execute a maneuver that causes you to overlap another ship, you may perform 1 free action.  Then receive 1 stress token."""
+            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."""

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


fix for when a custom loader is provided for an audio track

--- src/prong/track/audio.coffee
+++ src/prong/track/audio.coffee
@@ -80,8 +80,8 @@
             # on progress as it goes. The default one loads from http. Once
             # the loader is complete, the track should have channel and
             # buffer properties
-            if not ('_loader' in d)
-                if 'channel' in d
+            if not ('_loader' of d)
+                if '_channel' of d
                     # if we already have a channel set, set a 'do nothing' loader
                     d._loader = (_,callback) -> callback()
                 else


Trim remove_delay down closer to transition duration

--- app/js/jail_iframe/classes/notification.coffee
+++ app/js/jail_iframe/classes/notification.coffee
@@ -9,7 +9,7 @@
     type_classes: 'fl-message-icon-time fl-message-success'

 in_screen_time = 3000
-removal_delay = 1000 # Should be larger than notification_transition_time
+removal_delay = 350 # Should be larger than notification_transition_time
 content = """


fixing tests

fixing tests

--- math/expression_manipulation_spec.coffee
+++ math/expression_manipulation_spec.coffee
@@ -37,20 +37,19 @@
   describe "a proof-of-concept example", ->
     it "produces the correct result TODO make this larger", ->
       exp = @exp_builder()
-      position = ttm.lib.math.ExpressionPosition.build(position: exp.id(), expression: exp)
-
-      exp = @manip.build_append_number(value: 1).perform(exp, position).expression
-      exp = @manip.build_append_number(value: 0).perform(exp, position).expression
-      exp = @manip.build_append_multiplication().perform(exp, position).expression
-
-      exp = @manip.build_append_open_sub_expression().perform(exp, position).expression
-      exp = @manip.build_append_number(value: 2).perform(exp, position).expression
-      exp = @manip.build_append_addition().perform(exp, position).expression
-      exp = @manip.build_append_number(value: 4).perform(exp, position).expression
-      exp = @manip.build_close_sub_expression().perform(exp, position).expression
+      exp_pos = ttm.lib.math.ExpressionPosition.build(position: exp.id(), expression: exp)
+      exp_pos = @manip.build_append_number(value: 1).perform(exp_pos)
+      exp_pos = @manip.build_append_number(value: 0).perform(exp_pos)
+      exp_pos = @manip.build_append_multiplication().perform(exp_pos)
+
+      exp_pos = @manip.build_append_open_sub_expression().perform(exp_pos)
+      exp_pos = @manip.build_append_number(value: 2).perform(exp_pos)
+      exp_pos = @manip.build_append_addition().perform(exp_pos)
+      exp_pos = @manip.build_append_number(value: 4).perform(exp_pos)
+      exp_pos = @manip.build_close_sub_expression().perform(exp_pos)

       expected = @exp_builder(10, '*', [2, '+', 4])
-      expect(exp).toBeAnEqualExpressionTo expected
+      expect(exp_pos.expression()).toBeAnEqualExpressionTo expected

   describe "appending multiplication", ->
@@ -61,25 +60,21 @@
         @exp_pos_builder(10)

     it "does nothing if the expression is empty", ->
-      exp = @exp_builder()
-      position = ttm.lib.math.ExpressionPosition.build(position: exp.id(), expression: exp)
-      exp = @manip.build_append_multiplication().perform(exp, position).expression
+      exp_pos = @exp_pos_builder()
+      exp_pos = @manip.build_append_multiplication().perform(exp_pos)
       expected = @exp_builder()
-      expect(exp).toBeAnEqualExpressionTo expected
-
-    it "appends a multiplication", ->
-      @exp = @exp_builder(10)
+      expect(exp_pos.expression()).toBeAnEqualExpressionTo expected

     it "adds multiplication to the end of the expression", ->
-      exp = @exp_builder(1)
-      results = @manip.build_append_multiplication().perform(exp)
-      expect(results.expression.last() instanceof @components.classes.multiplication).toEqual true
+      exp_pos = @exp_pos_builder(1)
+      results = @manip.build_append_multiplication().perform(exp_pos)
+      expect(results.expression().last() instanceof @components.classes.multiplication).toEqual true

     it "correctly adds multiplication to an exponentiation", ->
-      exp = @exp_builder('^': [1, 2])
-      new_exp = @manip.build_append_multiplication().perform(exp)
+      exp_pos = @exp_pos_builder('^': [1, 2])
+      new_exp = @manip.build_append_multiplication().perform(exp_pos)
       expected = @exp_builder({'^': [1, 2]}, '*')
-      expect(new_exp.expression).toBeAnEqualExpressionTo expected
+      expect(new_exp.expression()).toBeAnEqualExpressionTo expected

     it_inserts_component_into_the_last_nested_open_expression(
       name: 'multiplication'


try again

try again

--- mechanics/001-binary-stars/csmain.coffee
+++ mechanics/001-binary-stars/csmain.coffee
@@ -26,12 +26,12 @@
         m1 = parseFloat(ps['m1'])
         m2 = parseFloat(ps['m2'])
         a = parseFloat(ps['a'])
-        theta = parseFloat(ps['theta'])
+        e = parseFloat(ps['e'])
         M = m1 + m2
         x1 = m2 / M * a
         x2 = - m1 / M * a

-        v  = Math.sqrt(au.G * M / a)
+        v  = Math.sqrt(au.G * M / a * (1 - e) / (1 + e))
         v1 = v / M * m2
         v2 = - v / M * m1

@@ -59,8 +59,8 @@

         vals = [
                 m1, m2,
-                x1, 0, v1 * Math.sin(theta), v1 * Math.cos(theta),
-                x2, 0, v2 * Math.sin(theta), v2 * Math.cos(theta)
+                x1, 0, 0, v1,
+                x2, 0, 0, v2
         ]
         invoke = ->
             worker.postMessage(vals)


fix https://github.com/sagemathinc/smc/issues/1147 -- make local hub read/write file lock have a timeout

--- src/smc-project/client.coffee
+++ src/smc-project/client.coffee
@@ -457,11 +457,12 @@
         @_file_io_lock ?= {}
         dbg = @dbg("write_file(path='#{opts.path}')")
         dbg()
-        if @_file_io_lock[path]?
+        now = new Date()
+        if now - (@_file_io_lock[path] ? 0) < 15000  # lock expires after 15 seconds (see https://github.com/sagemathinc/smc/issues/1147)
             dbg("LOCK")
             opts.cb("write_file -- file is currently being read or written")
             return
-        @_file_io_lock[path] = true
+        @_file_io_lock[path] = now
         dbg("@_file_io_lock = #{misc.to_json(@_file_io_lock)}")
         async.series([
             (cb) =>
@@ -489,11 +490,14 @@
         dbg = @dbg("path_read(path='#{opts.path}', maxsize_MB=#{opts.maxsize_MB})")
         dbg()
         @_file_io_lock ?= {}
-        if @_file_io_lock[path]?
+
+        now = new Date()
+        if now - (@_file_io_lock[path] ? 0) < 15000  # lock expires after 15 seconds (see https://github.com/sagemathinc/smc/issues/1147)
             dbg("LOCK")
             opts.cb("path_read -- file is currently being read or written")
             return
-        @_file_io_lock[path] = true
+        @_file_io_lock[path] = now
+
         dbg("@_file_io_lock = #{misc.to_json(@_file_io_lock)}")
         async.series([
             (cb) =>


(fix) Set correction unsaved after undo or redo.

--- app/scripts/pages/correction/correction.coffee
+++ app/scripts/pages/correction/correction.coffee
@@ -141,8 +141,12 @@
     $(window).off '.correction'
     $(document).off '.correction'

-  undo: -> @undoStack.undo()
-  redo: -> @undoStack.redo()
+  undo: ->
+    @undoStack.undo()
+    @isSaved false
+  redo: ->
+    @undoStack.redo()
+    @isSaved false

   save: ->
     @isSaving true


Change sign_in event to match a user logging in

--- src/smc-webapp/account.coffee
+++ src/smc-webapp/account.coffee
@@ -55,8 +55,6 @@
     setTimeout(f, 1)

 signed_in = (mesg) ->
-    {analytics_event} = require('./misc_page')
-    analytics_event('account', 'signed_in')    # user signed in
     # the has_remember_me cookie is for usability: After a sign in we "mark" this client as being "known"
     # next time the main landing page is visited, haproxy or hub will redirect to the client
     # note: similar code is in redux_account.coffee → AccountActions::sign_out
@@ -68,6 +66,8 @@
     console.log("Signed into #{mesg.hub} at #{new Date()}")
     if first_login
         first_login = false
+        {analytics_event} = require('./misc_page')
+        analytics_event('account', 'signed_in')    # user signed in
         if not misc_page.should_load_target_url()
             load_app ->
                 require('./history').load_target('projects')


webapp/account: updating comment about null vs. undefined

--- src/smc-webapp/r_account.cjsx
+++ src/smc-webapp/r_account.cjsx
@@ -1118,7 +1118,7 @@

     toggle_global_banner: (val) ->
         if val
-            # this must be null, not undefined. no idea why...
+            # this must be "null", not "undefined" – otherwise the data isn't stored in the DB.
             @on_change('show_global_info2', null)
         else
             @on_change('show_global_info2', webapp_client.server_time())


listen for 'disconnected' not 'disconnecting' to match changes in commit 692865c

--- src/client/index.coffee
+++ src/client/index.coffee
@@ -39,7 +39,7 @@
       c = new Connection origin

       del = -> delete connections[origin]
-      c.on 'disconnecting', del
+      c.on 'disconnected', del
       c.on 'connect failed', del
       connections[origin] = c


Fix the glsl box and a few other details

--- src/compile.glsl.coffee
+++ src/compile.glsl.coffee
@@ -5,7 +5,7 @@
 compileGLSL = (abstractSolidModel) ->
   distanceFunctions =
     sphereDist:
-      id: '__sphereDist'
+      id: '_sphereDist'
       returnType: 'float'
       arguments: ['vec3', 'float']
       code: do () ->
@@ -15,22 +15,21 @@
           "return length(#{position}) - #{radius};"
         ]
     boxDist:
-      id: '__boxDist'
+      id: '_boxDist'
       returnType: 'float'
       arguments: ['vec3', 'vec3']
       code: do () ->
         position = 'a'
         radius = 'b'
-        rel = 'r'
         dist = 's'
         [
           "if (all(lessThan(#{position}, #{radius})))"
           "  return 0.0;"
-          "vec3 #{dist} = max(vec3(0.0), #{rel} - #{position});"
+          "vec3 #{dist} = max(vec3(0.0), #{position} - #{radius});"
           "return max(max(#{dist}.x, #{dist}.y), #{dist}.z);"
         ]
     boxChamferDist:
-      id: '__boxChamferDist'
+      id: '_boxChamferDist'
       returnType: 'float'
       arguments: ['vec3', 'vec3', 'vec3', 'float']
       code: do () ->
@@ -206,7 +205,7 @@
         positionParam = "#{rayOrigin}"
         if center[0] != 0.0 or center[1] != 0.0 or center[2] != 0.0
           positionParam += " - vec3(#{center[0]},#{center[1]},#{center[2]})"
-        glslCode = "#{distanceFunctions.boxDist.id}(#{positionParam})"
+        glslCode = "#{distanceFunctions.boxDist.id}(#{positionParam}, vec3(#{boundaries[3] - center[0]}, #{boundaries[4] - center[1]}, #{boundaries[5] - center[2]}))"

   compileNode = (node, flags, glslFunctions) ->
     switch node.type


Define folding markers

Define folding markers

--- grammars/apl.cson
+++ grammars/apl.cson
@@ -2,6 +2,8 @@
 scopeName: "source.apl"
 fileTypes: ["apl", "dyalog"]
 firstLineMatch: '^#!.*\\b(apl|dyalog)'
+foldingStartMarker: '\\{'
+foldingStopMarker:  '\\}'
 patterns: [

 	{ include: "#strings" }


Add named capturing group for comment-matching

--- grammars/apl.cson
+++ grammars/apl.cson
@@ -11,6 +11,8 @@
 		name:  "comment.line.apl"
 		begin: "⍝"
 		end:   "$"
+		captures:
+			0: {name: "punctuation.definition.comment.apl"}
 	}


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/controllable.coffee
+++ src/controllable.coffee
@@ -1,6 +1,6 @@
 ###*
 The Controllable module adds simple movement
-when up, down, left, right are helf
+when up, down, left, right are held.

   # create a player and include ClampBounds


Change `batch` API

Change `batch` API

--- neo4jdriver.coffee
+++ neo4jdriver.coffee
@@ -65,8 +65,7 @@
         unless error
           @__cleanUpResponse response, (result) => @__proceedResult result
         else
-          console.error error
-          console.trace()
+          __error new Error error

   __cleanUpResponse: (response, cb) ->
     if response?.data and _.isObject response.data
@@ -75,13 +74,11 @@
       try
         @__cleanUpResults JSON.parse(response.content), cb
       catch error
-        console.error "Neo4j response error (Check your cypher queries):", [response.statusCode], error
-        console.error "Original received data:"
+        __error "Neo4j response error (Check your cypher queries):", [response.statusCode], error
+        __error "Originally received data:"
         console.log response.content
-        console.trace()
-    else
-      console.error "Empty response from Neo4j, but expecting data"
-      console.trace()
+    else
+      __error "Empty response from Neo4j, but expecting data"

   __cleanUpResults: (results, cb) ->
     if results?.results or results?.errors
@@ -89,7 +86,7 @@
       results = results?.results

     unless _.isEmpty errors
-      console.error error.code, error.message for error in errors
+      __error error.code, error.message for error in errors
     else if not _.isEmpty results
       cb result for result in results

@@ -99,8 +96,8 @@
         @emit result.id, null, result.body, result.id
       else
         for error in result.body.errors
-          console.error error.message
-          console.error {code: error.code}
+          __error error.message
+          __error {code: error.code}
         @emit result.id, error?.message, [], result.id
     else
       @emit result.id, null, [], result.id
@@ -124,26 +121,27 @@
         bound => callback error, if noTransform then response else @__transformData _.clone(response), reactive

   __connect: ->
-    response = @__call @root
-    if response?.statusCode
-      switch response.statusCode
-        when 200
-          if response.data.password_change_required
-            throw new Error "To connect to Neo4j - password change is required, please proceed to #{response.data.password_change}"
+    # response = @__call @root
+    @__call @root, {}, 'GET', (error, response) =>
+      if response?.statusCode
+        switch response.statusCode
+          when 200
+            if response.data.password_change_required
+              throw new Error "To connect to Neo4j - password change is required, please proceed to #{response.data.password_change}"
+            else
+              for key, endpoint of response.data
+                if _.isString endpoint
+                  if !!~endpoint.indexOf '://'
+                    @__service[key] = new Neo4jEndpoint key, endpoint, @
+                  else
+                    @__service[key] = get: -> endpoint
+                  __success "v#{endpoint}" if key is "neo4j_version"
+              @emit 'ready'
+              __success "Successfully connected to Neo4j on #{@url}"
           else
-            for key, endpoint of response.data
-              if _.isString endpoint
-                if !!~endpoint.indexOf '://'
-                  @__service[key] = new Neo4jEndpoint key, endpoint, @
-                else
-                  @__service[key] = get: -> endpoint
-                console.success "v#{endpoint}" if key is "neo4j_version"
-            @emit 'ready'
-            console.success "Successfully connected to Neo4j on #{@url}"
-        else
-          throw new Error JSON.stringify response
-    else
-      throw new Error "Error with connection to Neo4j"
+            throw new Error JSON.stringify response
+      else
+        throw new Error "Error with connection to Neo4j"

   __call: (url, options = {}, method = 'GET', callback) ->
     check url, String
@@ -186,9 +184,8 @@
       else
         return request method, url, options.data, options, callback
     catch error
-      console.error "Error sending request to Neo4j (GrapheneDB) server:"
-      console.error error
-      console.trace()
+      __error "Error sending request to Neo4j (GrapheneDB) server:"
+      __error new Error error

   __parseNode: (currentNode) ->
     if currentNode?.metadata or currentNode?.data
@@ -263,7 +260,7 @@
           parsed = parsed.concat @__parseResponse result.data, result.columns, reactive
         return parsed
       else
-        console.error response.exception
+        __error response.exception

     if response?.columns and response?.data
       unless _.isEmpty response.data
@@ -280,7 +277,7 @@
     unless callback
       return __wait (fut) =>
         @__batch task, (error, data) ->
-          console.error error if error
+          __error error if error
           fut.return new Neo4jCursor data
         , reactive
     else
@@ -514,12 +511,21 @@
   @param {String}   tasks.$.to - Endpoint (URL) for task
   @param {Number}   tasks.$.id - [Optional] Unique id to identify task. Should be always unique!
   @param {Object}   tasks.$.body - [Optional] JSONable object which will be sent as data to task
-  @param {Function} callback - callback function, if present `batch()` method will be called asynchronously
-  @param {Boolean}  reactive - if `true` and if `plain` is true data of node(s) will be updated before returning
-  @param {Boolean}  plain - if `true`, results will be returned as simple objects instead of Neo4jCursor
+  @param {Object}   settings
+  @param {Boolean}  settings.reactive - if `true` and if `plain` is true data of node(s) will be updated before returning
+  @param {Boolean}  settings.plain - if `true`, results will be returned as simple objects instead of Neo4jCursor
   @returns {[Object]} - array of Neo4jCursor(s) or array of Object id `plain` is `true`
   ###
-  batch: (tasks, callback, reactive = false, plain = false) ->
+  batch: (tasks, settings = {}, callback) ->
+    if _.isFunction settings
+      callback = settings
+      settings = {}
+    else
+      {reactive, plain} = settings
+
+    reactive ?= false
+    plain ?= false
+
     check tasks, [Object]
     check callback, Match.Optional Function
     check reactive, Boolean


View: bind functions specifically. no bindAll

--- coffee/src/views/view.coffee
+++ coffee/src/views/view.coffee
@@ -22,7 +22,7 @@
   # Events for view options.
   events: => {}

-  initialize: ->
+  initialize: =>
     super

     # Extend options with defaults.
@@ -31,18 +31,13 @@
     # If no eventhub is provided, this object is used as the eventhub.
     @eventhub = @options.eventhub || @

-    # Bind all functions within this object (including functions defined in
-    # derived classes) to `this`. This exempts you from having to bind
-    # functions to their respective objects throughout the codebase.
-    _.bindAll @
-
     # optionally add custom class names
     if @options.extraClasses
       _.each @options.extraClasses, (name) =>
         @$el.addClass name

   # Utility function for the complete removal of a View.
-  destroy: ->
+  destroy: =>
     @rendering = false
     @remove()
     @unbind()
@@ -52,7 +47,7 @@
   rendering: false

   # Render by default calls delegateEvents
-  render: ->
+  render: =>
     super

     @rendering = true
@@ -60,7 +55,7 @@
     @

   # Renders only if the view is in the ``rendering`` state.
-  softRender: ->
+  softRender: =>
     if @rendering
       @render()


test: removed empty util/test.coffee file


ComputeController: add ::stop method

ComputeController: add ::stop method

--- client/Main/providers/computecontroller.coffee
+++ client/Main/providers/computecontroller.coffee
@@ -128,6 +128,24 @@

       warn "start err:", err

+
+  stop: (machine)->
+
+    @eventListener.triggerState machine,
+      status      : Machine.State.Stopping
+      percentage  : 0
+
+    @kloud.stop { machineId: machine._id }
+
+    .then (res)=>
+
+      @eventListener.addListener 'stop', machine._id
+      log "stop res:", res
+
+    .catch (err)=>
+
+      warn "stop err:", err
+
       return Promise.resolve()


Action payload can be anything

Action payload can be anything

--- components/CleanAction.coffee
+++ components/CleanAction.coffee
@@ -6,7 +6,7 @@
   c.inPorts.add 'in',
     datatype: 'object'
   c.outPorts.add 'out',
-    datatype: 'object'
+    datatype: 'all'
   noflo.helpers.WirePattern c,
     async: true
     forwardGroups: true


calm-down: If env var HUBOT_LESS_MANATEES is set, don't check for caps

--- src/scripts/calm-down.coffee
+++ src/scripts/calm-down.coffee
@@ -19,7 +19,8 @@
     (that escalated quickly)
   ///i, (msg) -> msg.send manatee()

-  robot.hear ///
-    (([A-Z]{2,}\s+)([A-Z]{2,})\s?)|
-    ([A-Z]{5,})
-  ///, (msg) -> msg.send manatee()+  unless process.env.HUBOT_LESS_MANATEES
+    robot.hear ///
+      (([A-Z]{2,}\s+)([A-Z]{2,})\s?)|
+      ([A-Z]{5,})
+    ///, (msg) -> msg.send manatee()


remove backward compatible missingEnvironmentForApi

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -47,7 +47,7 @@
 module.exports = (robot) ->

   robot.respond /pager( me)?$/i, (msg) ->
-    if missingEnvironmentForApi(msg)
+    if pagerduty.missingEnvironmentForApi(msg)
       return

     campfireUserToPagerDutyUser msg, msg.message.user, (user) ->
@@ -74,7 +74,7 @@
     msg.send "Okay, I've forgotten your PagerDuty email"

   robot.respond /(pager|major)( me)? incident (.*)$/i, (msg) ->
-    if missingEnvironmentForApi(msg)
+    if pagerduty.missingEnvironmentForApi(msg)
       return

     pagerduty.getIncident msg.match[3], (err, incident) ->
@@ -109,7 +109,7 @@
   robot.respond /(pager|major)( me)? (?:trigger|page) ([\w\-]+) (.+)$/i, (msg) ->
     msg.finish()

-    if missingEnvironmentForApi(msg)
+    if pagerduty.missingEnvironmentForApi(msg)
       return

     fromUserName   = msg.message.user.name
@@ -171,7 +171,7 @@

   robot.respond /(?:pager|major)(?: me)? ack(?:nowledge)? (.+)$/i, (msg) ->
     msg.finish()
-    if missingEnvironmentForApi(msg)
+    if pagerduty.missingEnvironmentForApi(msg)
       return

     incidentNumbers = parseIncidentNumbers(msg.match[1])
@@ -181,7 +181,7 @@
     updateIncidents(msg, incidentNumbers, 'triggered,acknowledged', 'acknowledged')

   robot.respond /(pager|major)( me)? ack(nowledge)?(!)?$/i, (msg) ->
-    if missingEnvironmentForApi(msg)
+    if pagerduty.missingEnvironmentForApi(msg)
       return

     force = msg.match[4]?
@@ -213,7 +213,7 @@
   robot.respond /(?:pager|major)(?: me)? res(?:olve)?(?:d)? (.+)$/i, (msg) ->
     msg.finish()

-    if missingEnvironmentForApi(msg)
+    if pagerduty.missingEnvironmentForApi(msg)
       return

     incidentNumbers = parseIncidentNumbers(msg.match[1])
@@ -222,7 +222,7 @@
     updateIncidents(msg, incidentNumbers, 'triggered,acknowledged', 'resolved')

   robot.respond /(pager|major)( me)? res(olve)?(d)?(!)?$/i, (msg) ->
-    if missingEnvironmentForApi(msg)
+    if pagerduty.missingEnvironmentForApi(msg)
       return

     force = msg.match[5]?
@@ -252,7 +252,7 @@
   robot.respond /(pager|major)( me)? notes (.+)$/i, (msg) ->
     msg.finish()

-    if missingEnvironmentForApi(msg)
+    if pagerduty.missingEnvironmentForApi(msg)
       return

     incidentId = msg.match[3]
@@ -269,7 +269,7 @@
   robot.respond /(pager|major)( me)? note ([\d\w]+) (.+)$/i, (msg) ->
     msg.finish()

-    if missingEnvironmentForApi(msg)
+    if pagerduty.missingEnvironmentForApi(msg)
       return

     incidentId = msg.match[3]
@@ -299,7 +299,7 @@
     if msg.match[4]
       query['query'] = msg.match[4]

-    if missingEnvironmentForApi(msg)
+    if pagerduty.missingEnvironmentForApi(msg)
       return

     pagerduty.getSchedules query, (err, schedules) ->
@@ -316,7 +316,7 @@
         msg.send 'No schedules found!'

   robot.respond /(pager|major)( me)? (schedule|overrides)( ([\w\-]+))?( ([^ ]+))?$/i, (msg) ->
-    if missingEnvironmentForApi(msg)
+    if pagerduty.missingEnvironmentForApi(msg)
       return

     query = {
@@ -368,7 +368,7 @@
           msg.send "None found!"

   robot.respond /(pager|major)( me)? my schedule( ([^ ]+))?$/i, (msg) ->
-    if missingEnvironmentForApi(msg)
+    if pagerduty.missingEnvironmentForApi(msg)
       return

@@ -427,7 +427,7 @@
           msg.send 'No schedules found!'

   robot.respond /(pager|major)( me)? (override) ([\w\-]+) ([\w\-:\+]+) - ([\w\-:\+]+)( (.*))?$/i, (msg) ->
-    if missingEnvironmentForApi(msg)
+    if pagerduty.missingEnvironmentForApi(msg)
       return

     if msg.match[8]
@@ -472,7 +472,7 @@
           msg.send "Please use a http://momentjs.com/ compatible date!"

   robot.respond /(pager|major)( me)? (overrides?) ([\w\-]*) (delete) (.*)$/i, (msg) ->
-    if missingEnvironmentForApi(msg)
+    if pagerduty.missingEnvironmentForApi(msg)
       return

     withScheduleMatching msg, msg.match[4], (schedule) ->
@@ -488,7 +488,7 @@
   robot.respond /pager( me)? (.+) (\d+)$/i, (msg) ->
     msg.finish()

-    if missingEnvironmentForApi(msg)
+    if pagerduty.missingEnvironmentForApi(msg)
       return

     campfireUserToPagerDutyUser msg, msg.message.user, (user) ->
@@ -526,7 +526,7 @@

   # Am I on call?
   robot.respond /am i (on call|oncall|on-call)(.+)?/i, (msg) ->
-    if missingEnvironmentForApi(msg)
+    if pagerduty.missingEnvironmentForApi(msg)
       return

     campfireUserToPagerDutyUser msg, msg.message.user, (user) ->
@@ -556,7 +556,7 @@

   # who is on call?
   robot.respond /who(’s|'s|s| is|se)? (on call|oncall|on-call)( (?:for )?(.+))?/i, (msg) ->
-    if missingEnvironmentForApi(msg)
+    if pagerduty.missingEnvironmentForApi(msg)
       return

     scheduleName = msg.match[4]
@@ -580,7 +580,7 @@
           msg.send 'No schedules found!'

   robot.respond /(pager|major)( me)? services$/i, (msg) ->
-    if missingEnvironmentForApi(msg)
+    if pagerduty.missingEnvironmentForApi(msg)
       return

     pagerduty.get "/services", {}, (err, json) ->
@@ -598,7 +598,7 @@
         msg.send 'No services found!'

   robot.respond /(pager|major)( me)? maintenance (\d+) (.+)$/i, (msg) ->
-    if missingEnvironmentForApi(msg)
+    if pagerduty.missingEnvironmentForApi(msg)
       return

     campfireUserToPagerDutyUser msg, msg.message.user, (user) ->
@@ -631,8 +631,6 @@
   parseIncidentNumbers = (match) ->
     match.split(/[ ,]+/).map (incidentNumber) ->
       parseInt(incidentNumber)
-
-  missingEnvironmentForApi = pagerduty.missingEnvironmentForApi

   campfireUserToPagerDutyUser = (msg, user, required, cb) ->


minor fix for issue 495 -- stupid bug that rarely shows up

--- src/smc-webapp/editor.coffee
+++ src/smc-webapp/editor.coffee
@@ -1417,10 +1417,10 @@
             cb         : (err, mesg) =>
                 # TODO -- on error, we *might* consider saving to localStorage...
                 if err
-                    alert_message(type:"error", message:"Communications issue saving #{filename} -- #{err}")
+                    alert_message(type:"error", message:"Communications issue saving #{@filename} -- #{err}")
                     cb?(err)
                 else if mesg.event == 'error'
-                    alert_message(type:"error", message:"Error saving #{filename} -- #{to_json(mesg.error)}")
+                    alert_message(type:"error", message:"Error saving #{@filename} -- #{to_json(mesg.error)}")
                     cb?(mesg.error)
                 else
                     cb?()


broke published anon docs

broke published anon docs

--- src/smc-webapp/projects.cjsx
+++ src/smc-webapp/projects.cjsx
@@ -359,8 +359,11 @@
     # If a course payment is required for this project from the signed in user, returns time when
     # it will be required; otherwise, returns undefined.
     date_when_course_payment_required: (project_id) =>
+        account = @redux.getStore('account')
+        if not account?
+            return
         info = @get_course_info(project_id)
-        is_student = info?.get?('account_id') == salvus_client.account_id or info?.get?('email_address') == @redux.getStore('account').get('email_address')
+        is_student = info?.get?('account_id') == salvus_client.account_id or info?.get?('email_address') == account.get('email_address')
         if is_student and not @is_deleted(project_id)
             # signed in user is the student
             pay = info.get('pay')


fix spacing

fix spacing

--- src/smc-webapp/projects.cjsx
+++ src/smc-webapp/projects.cjsx
@@ -889,7 +889,6 @@
                     {@render_error()}
                 </Col>
             </Row>
-            <Space/>
             <Row>

                 {@render_upgrade_before_create(subs) if (require('./customize').commercial and @state.create_button_hit != '')}


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/string_extensions.coffee
+++ src/string_extensions.coffee
@@ -136,7 +136,7 @@

 @name startsWith
 @methodOf String#
-@param {String} s
+@param {String} str The string to che

 @returns {Boolean} True if this string starts with the given string, false otherwise.
 ###


unused attributes can be auto-sorted alphabetically via 'autoSortUnusedAttrs: true' option

--- pivot.coffee
+++ pivot.coffee
@@ -451,6 +451,7 @@
         menuLimit: 200
         cols: [], rows: [], vals: []
         unusedAttrsVertical: false
+        autoSortUnusedAttrs: false
         rendererOptions: null
         localeStrings:
             renderError: "An error occurred rendering the PivotTable results."
@@ -641,6 +642,13 @@
                 localeStrings: opts.localeStrings
                 rendererOptions: opts.rendererOptions

+            # if requested make sure unused columns are in alphabetical order
+            if opts.autoSortUnusedAttrs
+                unusedAttrsContainer = $("td#unused.pvtAxisContainer")
+                $(unusedAttrsContainer).children("li").sort((a, b) ->
+                    (if $(a).text().toLowerCase() > $(b).text().toLowerCase() then 1 else -1)
+                ).appendTo unusedAttrsContainer
+
         #the very first refresh will actually display the table
         refresh()


Don't error out if capitilize helper gets null as an argument

--- assets/scripts/app/helpers/handlebars.coffee
+++ assets/scripts/app/helpers/handlebars.coffee
@@ -10,7 +10,10 @@
   safe I18n.t(key)

 Ember.registerBoundHelper 'capitalize', (value, options) ->
-  safe $.capitalize(value)
+  if value?
+    safe $.capitalize(value)
+  else
+    ''

 Ember.registerBoundHelper 'formatTime', (value, options) ->
   safe Travis.Helpers.timeAgoInWords(value) || '-'


Add internal note

Add internal note

--- bokehjs/src/coffee/common/build_views.coffee
+++ bokehjs/src/coffee/common/build_views.coffee
@@ -49,5 +49,6 @@
 # FIXME Hack to expose jQueryUIPrefixer
 build_views.jQueryUIPrefixer = jQueryUIPrefixer

+# FIXME This export is the same as module.exports = build_views
 module.exports =
   build_views = build_views


Fixed row link in answer row

Fixed row link in answer row

--- app/assets/javascripts/answers.coffee
+++ app/assets/javascripts/answers.coffee
@@ -9,7 +9,7 @@
       "info": false,
       "autoWidth": true

-    $("tr[data-link]").click ->
+    $(document).on "click", "tr[data-link]", ->
       url = $(this).data("link")
       window.open(url, "_blank")


Set KONFIG_PROJECTROOT relatively to run script path

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

--- config/generateRunFile.coffee
+++ config/generateRunFile.coffee
@@ -44,6 +44,8 @@
     #!/bin/bash

     # ------ THIS FILE IS AUTO-GENERATED ON EACH BUILD ----- #
+
+    export KONFIG_PROJECTROOT=$(cd $(dirname $0); pwd)

     ENV_SHELL_FILE=${ENV_SHELL_FILE:-$(dirname $0)/.env.sh}
     if [ -f "$ENV_SHELL_FILE" ]; then


Add unit icon to the info box

Add unit icon to the info box

--- app/assets/javascripts/battles.coffee
+++ app/assets/javascripts/battles.coffee
@@ -152,7 +152,7 @@
 updateUnitInfoBox = (unit, isCurrent) ->
   id = if isCurrent then "#selected-unit" else "#targeted-unit"

-  $("#{id} .info-header").html("
 #{unit.name}
")
+  $("#{id} .info-header").html("
 #{unit.name}
")

   $("#{id} .info-hp").html("

#{unit.currentHP}

")
   $("#{id} .other-info").html("")


falsey values in js suck

falsey values in js suck

--- app/assets/javascripts/glimpse.coffee
+++ app/assets/javascripts/glimpse.coffee
@@ -12,7 +12,8 @@

   if event.keyCode == 96
     $('#glimpse').toggleClass 'disabled'
-    $.cookie('glimpse', !$.cookie('glimpse'))
+    enable = $.cookie('glimpse') == 'false'
+    $.cookie('glimpse', enable)

 $(document).on 'pjax:end', updatePerformanceBar
 $(document).on 'keypress', toggleBar


Remove songs from songlist when form submission completes Fix for truecolor (for visual feedback)!

--- app/assets/javascripts/init.js.coffee
+++ app/assets/javascripts/init.js.coffee
@@ -4,7 +4,9 @@
 update_original_var = undefined

 ceAfterUpdate = (el) ->
+  $(element).closest('table').removeClass 'table-hover'
   trueColor = $(el).css 'backgroundColor'
+  $(element).closest('table').addClass 'table-hover'
   $(el).animate { backgroundColor: '#cce2ff' },
     duration: 100
     complete: ->
@@ -76,10 +78,10 @@
       console.log e
     mc = $('<canvas height=250>')
     ctx = mc.get(0).getContext('2d')
-    myLightboxChart = new Chart(ctx).Doughnut data.leader_usage_data, animationSteps: 60
+    myLightboxChart = new Chart(ctx).Doughnut data.leader_usage_data
     moc = $('<canvas height=80>')
     ctx2 = moc.get(0).getContext('2d')
-    myOtherLightboxChart = new Chart(ctx2).Bar data.song_frequency_data, animationSteps: 60
+    myOtherLightboxChart = new Chart(ctx2).Bar data.song_frequency_data, "showTooltips": false, scaleBeginAtZero: true
     tabledata = ''
     for key of data.song_details
       tabledata += '
' + key + '	' + data.song_details[key] + '
' if data.song_details.hasOwnProperty key
@@ -181,6 +183,7 @@
       when 'created'
         switch response.responseJSON.whatCreated
           when 'service'
+            $('#songList').tagit "removeAll"
             $('.insertionForm').after response.responseJSON.htmlOutput
           when 'leader'
             $('#service_leader_id').append response.responseJSON.htmlOutput
@@ -222,9 +225,6 @@
     allowNewTags: false
     onlyAvailableTags: true
     removeConfirmation: true
-    onSubmit: ->
-      $('#songList').tagit 'removeAll'
-      return
     onTagClicked: (evt, ui) ->
       tagitSongAnchorClicked ui.tagLabel
       return


fix(kanban): prevent initial animations

fix(kanban): prevent initial animations

--- app/coffee/modules/kanban/main.coffee
+++ app/coffee/modules/kanban/main.coffee
@@ -430,7 +430,9 @@
         @.fillUsersAndRoles(project.members, project.roles)
         @.initializeSubscription()
         @.loadKanban().then () =>
-            @.initialLoad = true
+            @timeout () =>
+                @.initialLoad = true
+            , 0, false

         @.generateFilters()