abstracted away input type

abstracted away input type

--- src/mist-ninja-builder.coffee
+++ src/mist-ninja-builder.coffee
@@ -80,6 +80,12 @@
     hash = MistNinjaBuilder.hashCommand command
     @addRule hash, command, vars

+  mapInputs: ()-> (input)=>
+    switch input.type
+      when 'glob' then MistGlobber.performGlob input.glob, @mistDir
+      else
+        throw "unknown input type '#{input.type}'"
+
   addTarget: (statement)->
     command = @delimitCommand statement.command
     commandHash = MistNinjaBuilder.hashCommand command
@@ -91,10 +97,8 @@

     targets = []

-
     if statement.main_inputs.length
-      statement.main_inputs =
-        MistGlobber.doAllGlobs statement.main_inputs, @mistDir
+      statement.main_inputs = statement.main_inputs.map(@mapInputs).flatten()

     if statement.foreach
       for inp in statement.main_inputs
@@ -118,9 +122,9 @@
         @delimitAll target.order_inputs, build_vars

       target.dep_inputs =
-        MistGlobber.doAllGlobs target.dep_inputs, @mistDir
+        target.dep_inputs.map(@mapInputs).flatten()
       target.order_inputs =
-        MistGlobber.doAllGlobs target.order_inputs, @mistDir
+        target.order_inputs.map(@mapInputs).flatten()

       target.main_outputs =


Removed commented code

Removed commented code

--- prototypes/using-sirius/coffeescripts/extensions/Od.coffee
+++ prototypes/using-sirius/coffeescripts/extensions/Od.coffee
@@ -10,15 +10,14 @@
       @get('link_id_origin'),
       @get('link_id_destination')
     ]
-#    [begin_node, end_node] = [object_with_id.node[begin], object_with_id.node[end]]
+
     [n_origin, n_destination, l_origin, l_destination] = [
       object_with_id.network[n_origin_id],
       object_with_id.network[n_destination_id],
       object_with_id.link[l_origin_id],
       object_with_id.link[l_destination_id]
     ]
-#    @set('begin_node', begin_node)
-#    @set('end_node', end_node)
+
     @set('network_origin', n_origin)
     @set('network_destination', n_destination)
     @set('link_origin', l_origin)


Destroying async repository when HgRespository is destroyed. (#12)

--- lib/hg-repository.coffee
+++ lib/hg-repository.coffee
@@ -81,6 +81,10 @@
     if @subscriptions?
       @subscriptions.dispose()
       @subscriptions = null
+
+    if @async?
+      @async.destroy()
+      @async = null

   ###
   Section: Event Subscription


better acceleration, still doesn't account for elapsed time

--- scrolling/player-controller.coffee
+++ scrolling/player-controller.coffee
@@ -8,6 +8,9 @@

     xVelocity: 0
     yVelocity: 0
+
+    accelerationStep: 0.2
+    accelerationCap: 5

     hitBox: null

@@ -25,26 +28,30 @@
         stage.addChild @hitBox

     moveRight: ->
-        @xVelocity += 1
+        @xVelocity += @accelerationStep
         @capVelocity()

     moveLeft: ->
-        @xVelocity -= 1
+        @xVelocity -= @accelerationStep
         @capVelocity()

     slow: ->
         if @xVelocity < 0
-            @xVelocity += 1
+            @xVelocity += @accelerationStep
+            if @xVelocity > 0
+                @xVelocity = 0

         else if @xVelocity > 0
-            @xVelocity -= 1
+            @xVelocity -= @accelerationStep
+            if @xVelocity < 0
+                @xVelocity = 0

     capVelocity: ->
-        if @xVelocity < -4
-            @xVelocity = -4
+        if @xVelocity < -@accelerationCap
+            @xVelocity = -@accelerationCap

-        if @xVelocity > 4
-            @xVelocity = 4
+        if @xVelocity > @accelerationCap
+            @xVelocity = @accelerationCap

     update: ->
         @hitBox.position.x += @xVelocity


Re-factoring, usage remains the same

Re-factoring, usage remains the same

--- lib/generators/nas_extjs/templates/public/app/lib/SaveNotify.coffee
+++ lib/generators/nas_extjs/templates/public/app/lib/SaveNotify.coffee
@@ -24,29 +24,28 @@
         model.save( options )

     _onSuccess: (rec,op)->
+        @args   = arguments
+        @status = 'success'
         this._sceduleDestruct()
-        @mask.hide()
-        @mask.msg = "Success!"
-        this.mask.msgEl.addCls('success')
-        @mask.show()
-        if Ext.isFunction( @saveOptions.success )
-            Ext.callback( @saveOptions.success, @saveOptions.scope,[ rec, op ] )

     _onFailure: (rec,op)->
+        @args   = arguments
+        @status = 'failure'
         this._sceduleDestruct()
-        @mask.hide()
-        @mask.msg = "Failed"
-        this.mask.msgEl.addCls('failure')
-        @mask.show()
-        if @saveOptions.failure
-           Ext.callback( @saveOptions.failure, @saveOptions.scope,[ rec, op ] )
-

     _sceduleDestruct: ->
+        @mask.hide()
+        @mask.msg = Ext.String.capitalize( @status + '!')
+        this.mask.msgEl.addCls( @status )
+        @mask.show()
         ( new Ext.util.DelayedTask( this._destroyMask, this ) ).delay( 1500 )

     _destroyMask: ->
         @mask.destroy()
+        if Ext.isFunction( @saveOptions[@status] )
+            Ext.callback( @saveOptions[@status], @saveOptions.scope, @args )
+
+

 App.SaveNotify = App.lib.SaveNotify.createAndShow


groups: invitation modals minor styling

groups: invitation modals minor styling

--- client/landing/app/Applications/Groups.kdapplication/Views/groupsinvitationrequestsview.coffee
+++ client/landing/app/Applications/Groups.kdapplication/Views/groupsinvitationrequestsview.coffee
@@ -42,7 +42,7 @@
       controller.hideLazyLoader()
       return warn err if err
       controller.removeAllItems() if removeAllItems
-      controller.instantiateListItems requests
+      controller.instantiateListItems requests
       if requests?.length > 0
         controller.setLastTimestamp requests.last.timestamp_
         controller.emit 'teasersLoaded', requests.length
@@ -129,8 +129,8 @@
     modal = new KDModalViewWithForms
       title                  : options.title
       overlay                : yes
-      width                  : 300
-      height                 : 'auto'
+      width                  : options.width or 400
+      height                 : options.height or 'auto'
       tabs                   :
         forms                :
           invite             :
@@ -144,7 +144,7 @@
                   diameter   : 12
               Cancel         :
                 style        : 'modal-cancel'
-                callback     : (event)=> modal.destroy()
+                callback     : -> modal.destroy()
             fields           : options.fields

     form = modal.modalTabs.forms.invite
@@ -219,7 +219,7 @@

   showErrorMessage:(err)->
     warn err
-    new KDNotificationView
+    new KDNotificationView
       title    : if err.name is 'KodingError' then err.message else 'An error occured! Please try again later.'
       duration : 2000


Injecting session name in `browser_conf`

--- tests/www/tests/runner.coffee
+++ tests/www/tests/runner.coffee
@@ -42,6 +42,9 @@
   # loop through all browser configs
   for browser_conf in browsers

+    # computes session name based on the last two tags
+    browser_conf.name = (browser_conf.tags.slice 1 ).join '_'
+
     # when testing local
     if env is 'local'

@@ -49,10 +52,8 @@
       browser_conf.platform = null
       browser_conf.version = null

-
     # 2nd root suite - based on browser
-    browser_name = (browser_conf.tags.slice 1 ).join '_'
-    describe "[#{browser_name}]", ->
+    describe "[#{browser_conf.name}]", ->

       # INIT WD
       if env is 'local'


flake8 max line length override

flake8 max line length override

--- .atom/config.cson
+++ .atom/config.cson
@@ -110,6 +110,8 @@
       runTestsWithShortFlag: false
   "goto-definition": {}
   linter: {}
+  "linter-flake8":
+    maxLineLength: 160
   "linter-jscs":
     displayAs: "warning"
     onlyConfig: true


Overlapping cluster edge detection.

Overlapping cluster edge detection.

--- packages/landsofillusions-assets/engine/mesh/computeedges.coffee
+++ packages/landsofillusions-assets/engine/mesh/computeedges.coffee
@@ -17,8 +17,8 @@
       for pixel in clusterA.pixels
         coordinates = clusterA.getAbsolutePixelCoordinates pixel

-        # Detect edges with neighboring pixels on all 4 sides. Note: Edge vertices are
-        # directed so that cluster A is on the right of the segment, cluster B on the left.
+        # Detect edges with neighboring pixels on all 4 sides. Edge vertices are directed
+        # so that cluster A is on the right of the segment, cluster B on the left.
         if pixel.clusterEdges.left and clusterB.findPixelAtAbsoluteCoordinate(coordinates.x - 1, coordinates.y)?.clusterEdges.right
           edge.addSegment coordinates, 0, 1, 0, 0

@@ -29,6 +29,19 @@
           edge.addSegment coordinates, 0, 0, 1, 0

         if pixel.clusterEdges.down and clusterB.findPixelAtAbsoluteCoordinate(coordinates.x, coordinates.y + 1)?.clusterEdges.up
+          edge.addSegment coordinates, 1, 1, 0, 1
+
+        # Detect edges with overlapping pixels.
+        if pixel.clusterEdges.left and clusterB.findPixelAtAbsoluteCoordinate(coordinates.x , coordinates.y)?.clusterEdges.left
+          edge.addSegment coordinates, 0, 1, 0, 0
+
+        if pixel.clusterEdges.right and clusterB.findPixelAtAbsoluteCoordinate(coordinates.x, coordinates.y)?.clusterEdges.right
+          edge.addSegment coordinates, 1, 0, 1, 1
+
+        if pixel.clusterEdges.up and clusterB.findPixelAtAbsoluteCoordinate(coordinates.x, coordinates.y)?.clusterEdges.up
+          edge.addSegment coordinates, 0, 0, 1, 0
+
+        if pixel.clusterEdges.down and clusterB.findPixelAtAbsoluteCoordinate(coordinates.x, coordinates.y)?.clusterEdges.down
           edge.addSegment coordinates, 1, 1, 0, 1

       console.log "Computed edge between clusters #{clusterIndexA} and #{clusterIndexB}", edge if LOI.Assets.Engine.Mesh.debug


finish sync scroll

finish sync scroll

--- app/assets/javascripts/app.coffee
+++ app/assets/javascripts/app.coffee
@@ -14,3 +14,33 @@
         $("#md_html_div").html(marked(editor.getValue()))
         $('pre code').each (i, block) ->
             hljs.highlightBlock(block)
+
+    # sync scroll between two columns
+    # check: http://stackoverflow.com/questions/18952623/synchronized-scrolling-using-jquery
+    # sync scroll: md -> html
+    editor_scroll_handler = (scroll) =>
+        percentage = scroll / (editor.session.getScreenLength() * \
+            editor.renderer.lineHeight - ($(editor.renderer.getContainerElement()).height()))
+        if percentage > 1 or percentage < 0 then return
+        percentage = Math.floor(percentage * 1000) / 1000;
+        # detach other's scroll handler first
+        $("#md_html_div").off('scroll', html_scroll_handler)
+        md_html_div = $("#md_html_div").get(0)
+        md_html_div.scrollTop = percentage * (md_html_div.scrollHeight - md_html_div.offsetHeight)
+        # re-attach other's scroll handler at the end, with some delay
+        setTimeout (-> $("#md_html_div").scroll(html_scroll_handler)), 10
+    editor.session.on('changeScrollTop', editor_scroll_handler)
+
+    # sync scroll: html -> md
+    html_scroll_handler = (e) =>
+        md_html_div = $("#md_html_div").get(0)
+        percentage = md_html_div.scrollTop / (md_html_div.scrollHeight - md_html_div.offsetHeight)
+        if percentage > 1 or percentage < 0 then return
+        percentage = Math.floor(percentage * 1000) / 1000;
+        # detach other's scroll handler first
+        editor.getSession().removeListener('changeScrollTop', editor_scroll_handler);
+        editor.session.setScrollTop((editor.session.getScreenLength() * \
+            editor.renderer.lineHeight - $(editor.renderer.getContainerElement()).height()) * percentage)
+        # re-attach other's scroll handler at the end, with some delay
+        setTimeout (-> editor.session.on('changeScrollTop', editor_scroll_handler)), 20
+    $("#md_html_div").scroll(html_scroll_handler)


fixed examples for final static in java #26

--- src/parser/java-parser.coffee
+++ src/parser/java-parser.coffee
@@ -126,7 +126,7 @@
         {
           key: "allcaps", display: "Constant name is all caps with underscore(_)",
           code: """
-                final String FOO_BAR = \"baz\";
+                final static String FOO_BAR = \"baz\";

                 static final String FOO_BAR = \"baz\";
                 """
@@ -134,7 +134,7 @@
         {
           key: "notallcaps", display: "Constant name is not all caps",
           code: """
-                final String foobar = \"baz\";
+                final static String foobar = \"baz\";

                 static final String foobar = \"baz\";
                 """


Fix linting error

Fix linting error

--- src/beautifiers/phpcbf.coffee
+++ src/beautifiers/phpcbf.coffee
@@ -22,7 +22,7 @@
   beautify: (text, language, options) ->
     @debug('phpcbf', options)
     standardFiles = ['phpcs.xml', 'phpcs.xml.dist', 'phpcs.ruleset.xml', 'ruleset.xml']
-    standardFile = @findFile(atom.project.getPaths()[0], standardFiles);
+    standardFile = @findFile(atom.project.getPaths()[0], standardFiles)

     options.standard = standardFile if standardFile


application.spec: Fix failing app creation test w/ too short name

Resolves: #598
Change-type: patch
Signed-off-by: Thodoris Greasidis <5791bdc4541e79bd43d586e3c3eff1e39c16665f@balena.io>

--- tests/integration/models/application.spec.coffee
+++ tests/integration/models/application.spec.coffee
@@ -88,15 +88,17 @@
 					deviceType: 'edge'
 				m.chai.expect(promise).to.be.rejectedWith('Discontinued device type: edge')

-			it 'should be rejected if the name has less than three characters', ->
+			it 'should be rejected if the name has less than four characters', ->
 				promise = resin.models.application.create
-					name: 'Fo'
+					name: 'Foo'
 					applicationType: 'microservices-starter'
 					deviceType: 'raspberry-pi'
 				m.chai.expect(promise).to.be.rejected
 				.then (error) ->
+					m.chai.expect(error).to.have.property('code', 'ResinRequestError')
+					m.chai.expect(error).to.have.property('statusCode', 400)
 					m.chai.expect(error).to.have.property('message')
-					.that.contains('It is necessary that each app name that is of an organization, has a Length (Type) that is greater than or equal to 4')
+					.that.contains('It is necessary that each application has an app name that has a Length (Type) that is greater than or equal to 4')

 			it 'should be able to create an application using a device type alias', ->
 				resin.models.application.create


proper linking

proper linking

--- generators/generate.coffee
+++ generators/generate.coffee
@@ -30,7 +30,7 @@
      }, {
       names: ['links', 'l']
       type: 'arrayOfString'
-      help: 'External links'
+      help: 'External links in format `docker-host[:as-host]`'
       default: []
      }, {
       names: ['compose', 'c']
@@ -40,7 +40,7 @@
      }, {
       names: ['environment', 'e']
       type: 'arrayOfString'
-      help: 'Environment key=value'
+      help: 'Environment `key=value` pairs'
       default: []
      }, {
       names: ['no-defaults', 'n']
@@ -106,12 +106,14 @@
 writeProjectEnv = () =>

   for link in options.links
+    [origServer, linkServer] = link.split(/:(.+)?/)
+    linkServer ?= origServer
     if link.match /redis/
-      environment.REDIS_URI = "redis://#{link}:6379"
-      environment.REDIS_HOST = link
+      environment.REDIS_URI = "redis://#{linkServer}:6379"
+      environment.REDIS_HOST = linkServer
       environment.REDIS_PORT = 6379
     if link.match /mongo/
-      environment.MONGODB_URI = "mongodb://#{link}/db"
+      environment.MONGODB_URI = "mongodb://#{linkServer}/#{linkServer}"

   readEnv "#{options.stack_env}/#{templateData.projectName}/env", writeData


now selectionTool woorks like it used to

--- src/coffee/bokeh_views.coffee
+++ src/coffee/bokeh_views.coffee
@@ -581,7 +581,6 @@
     for idx in [0..@screeny.length]
       if color_field
         comp_color = color_mapper.map_screen(idx)
-        #console.log("comp_color #{comp_color}")
         @plot_view.ctx.strokeStyle = comp_color
         @plot_view.ctx.fillStyle = comp_color
       if mark_type == "square"
@@ -828,37 +827,37 @@
       if renderer.get_ref('data_source').get('selecting') == false
         #skip data sources which are not selecting'
         continue
-      for idx in renderer.get_ref('data_source').get('selected')
-        selected[String(idx)] = true
-      node = d3.select(rendererview.el)
-      node.selectAll(renderer.get('mark')).filter((d, i) =>
-        return not selected[String(i)]
-      ).attr('fill', @mget('unselected_color'))
-
-      marks = node.selectAll(renderer.get('mark')).filter((d, i) =>
-        return selected[String(i)]
-      )
-      @plotview.renderers[renderer.id].fill_marks(marks)
-    return null
-
-  addPolygon: (x,y) ->
-    @plot_view.ctx.fillRect(x,y,5,5)
-
-  render : () ->
-    window.overlay_render += 1
-    super()
-    for temp in _.zip(@mget('renderers'), @rendererviews)
-      [renderer, rendererview] = temp
-      renderer = @model.resolve_ref(renderer)
-      selected = {}
-      if renderer.get_ref('data_source').get('selecting') == false
-        #skip data sources which are not selecting'
-        continue
-      @plot_view.ctx.fillStyle = 'orange'
-      @plot_view.ctx.strokeStyle = 'orange'
-      for idx in renderer.get_ref('data_source').get('selected')
-        @addPolygon(rendererview.screenx[idx], rendererview.screeny[idx])
-      @plot_view.ctx.stroke()
+      sel_idxs = renderer.get_ref('data_source').get('selected')
+      ds = renderer.get_ref('data_source')
+      data = ds.get('data')
+      fcolor = @mget('foreground_color')
+      rvm = rendererview.model
+
+      fcolor = rvm.get('foreground_color')
+      unselected_color = @mget('unselected_color')
+      color_field = rvm.get('color_field')
+      if color_field
+        color_mapper = rvm.get_ref('color_mapper')
+      color_arr = rvm.get('color_field')
+      mark_type = @mget('mark')
+      for idx in [0..data.length]
+        if idx in sel_idxs
+          if color_field
+            comp_color = color_mapper.map_screen(idx)
+            @plot_view.ctx.strokeStyle = comp_color
+            @plot_view.ctx.fillStyle = comp_color
+          else
+            @plot_view.ctx.strokeStyle = fcolor
+            @plot_view.ctx.fillStyle = fcolor
+
+        else
+          @plot_view.ctx.fillStyle = unselected_color
+          @plot_view.ctx.strokeStyle = unselected_color
+        if mark_type == "square"
+          @addPolygon(rendererview.screenx[idx], rendererview.screeny[idx])
+        else
+          @addCircle(rendererview.screenx[idx], rendererview.screeny[idx])
+        @plot_view.ctx.stroke()
     return null


WIP: assets are base64 encoded

WIP: assets are base64 encoded

--- platform/tools/convert_from_directory.coffee
+++ platform/tools/convert_from_directory.coffee
@@ -4,6 +4,7 @@
 escodegen = require("escodegen")
 esprima = require("esprima")
 fs = require("fs.extra")
+mime = require("mime")
 path = require("path")
 util = require("util")

@@ -36,28 +37,32 @@
       util.error("How do I handle parsed type #{parsed.type}?")
       process.exit(2)

+createDataUri = (filename) ->
+  buffer = fs.readFileSync(filename)
+  mimeType = mime.lookup(filename)
+  return "data:#{mimeType};#{buffer.toString('base64')}"
+

 # MAIN
 if process.argv.length < 4
-  util.error("Usage: coffee convert_from_directory.coffee <input_dir> <output_dir>")
+  util.error("Usage: coffee convert_from_directory.coffee <input_dir> <output_file>")
   process.exit(1)

 inputDir = process.argv[2]
 outputDir = process.argv[3]

-# TODO: remove output directory before starting?
-fs.mkdirRecursiveSync(outputDir)
-
 # This object will be written out as JSON at the end
 outputObj =
+  version: OUTPUT_VERSION
+  model: {}
+  layout: {}
   actions: {}
+  tools: {}
   assets: {}
-  layout: {}
-  model: {}
-  version: OUTPUT_VERSION

 # Actions are a JS file that needs to be parsed
 parsedActions = esprima.parse(fs.readFileSync(path.join(inputDir, "actions.js"), { encoding: "utf8" }))
+# TODO: remove references to "this"
 outputObj.actions = parsedToObj(parsedActions.body[0].expression)

 # Copy over layout JSON
@@ -66,19 +71,11 @@
 # Copy over model JSON
 outputObj.model = JSON.parse(fs.readFileSync(path.join(inputDir, "model.json"), { encoding: "utf8" }))

-# Copy over asset JSON
-outputObj.assets = JSON.parse(fs.readFileSync(path.join(inputDir, "assets.json"), { encoding: "utf8" }))
+# TODO: Copy over tools

-# TODO: create base64 version of all assets
-
-# Copy referenced assets
-for name, filepath of outputObj.assets
-  # Remove extra directories for output destination
-  filename = path.basename(filepath)
-  outputObj.assets[name] = filename
-  do (filepath, filename) ->
-    fs.copy path.join(inputDir, filepath), path.join(outputDir, filename), (err) ->
-      if err?
-        util.error("WARNING: Cannot copy #{filename}: #{err}")
+# Create data-URI encoded versions of all assets
+assetMap = JSON.parse(fs.readFileSync(path.join(inputDir, "assets.json"), { encoding: "utf8" }))
+for name, filename of outputObj.assets
+  outputObj.assets[name] = createDataUri(filename)

 fs.writeFileSync(path.join(outputDir, "game.json"), toJson(outputObj))


Client: Common: Updated fetchAccount util to return correct account data

--- client/app/lib/util/fetchAccounts.coffee
+++ client/app/lib/util/fetchAccounts.coffee
@@ -18,7 +18,7 @@
   queue = origins.map (origin) -> (fin) ->
     fetchAccount origin, (err, account) ->
       return fin err  if err
-      fin account
+      fin null, account

   # async aggregates account objects, returns in form of (err, [account, ...])
   async.parallel queue, callback


.

Former-commit-id: 8158f0c7a869b5defa7b61b6441bdd11506de8f4 [formerly 7d22291765e88324234b9cb51051fbb15a11622b] [formerly cd06081d4282cf308b83118e7174a68548961d71 [formerly fad9b1342bec79ba30dc573f3aa58b64094877fb]] [formerly 3744487b198c16f12973abeb6f948e95dd920034 [formerly a8a8b4bdaeafe496a8b5939f79e89bccb92eabe4] [formerly 030f4f1166c7e4e606dfe6dc69ea6dcaa9ed7fba [formerly 030f4f1166c7e4e606dfe6dc69ea6dcaa9ed7fba [formerly 4a48bf0c6639f583db2ce430c5942a2e878ab546]]]]
Former-commit-id: d7368a6dad3f606fdce37ccad0097f9c6b16b125 [formerly 1d5aceee5811a3d861f5a59ba30c762f3fb2e27b] [formerly 60dd3dd86d08d409b3f68f913fc66dece08a0509 [formerly 3af19f75d3c8741fa0b2b298ff625661cfe22685]]
Former-commit-id: 96e265948d3f743a15c8c73af9de469e83e1482b [formerly 282165dba052b6fd6a955dd233d1266cafb99697]
Former-commit-id: 93eccf2318dc8753024c771b249ed5cd5ee3f9b3
Former-commit-id: 4f44dbfdac17bb526d8c9e46a5848ff051f5e928

--- @client/profile_menu.coffee
+++ @client/profile_menu.coffee
@@ -110,7 +110,7 @@
                     "Then take a photo of yourself with it, and email the photo to "

                     A
-                      mailTo: 'verify@consider.it'
+                      href: 'mailto:verify@consider.it'
                       style:
                         textDecoration: 'underline'
                       'verify@consider.it'


adding code sample for #wrap

adding code sample for #wrap

--- src/array_extensions.coffee
+++ src/array_extensions.coffee
@@ -302,12 +302,12 @@
 passed as in each</each>.

-  results = []
-  [1, 2, 3, 4].eachSlice 2, (slice) ->
-    results.push(slice)
+   results = []
+   [1, 2, 3, 4].eachSlice 2, (slice) ->
+     results.push(slice)
 => [1, 2, 3, 4]

-  results
+   results
 => [[1, 2], [3, 4]]

@@ -356,7 +356,7 @@
 Returns the first element of the array, undefined if the array is empty.

-  ["first", "second", "third"].first()
+   ["first", "second", "third"].first()
 => "first"

@@ -390,7 +390,7 @@
 Returns an object containing the extremes of this array.

-  [-1, 3, 0].extremes()
+   [-1, 3, 0].extremes()
 => {min: -1, max: 3}

@@ -435,6 +435,17 @@
 If length is not given return the element at start, again assuming the array
 is a circle.

+

+   [1, 2, 3].wrap(-1)
+=> 3
+
+   [1, 2, 3].wrap(6)
+=> 1
+
+   ["l", "o", "o", "p"].wrap(0, 16)
+=> ["l", "o", "o", "p", "l", "o", "o", "p", "l", "o", "o", "p", "l", "o", "o", "p"]
+

+
 @name wrap
 @methodOf Array#


Disable collaboration test sets

Disable collaboration test sets

--- scripts/test-instance/parallel-sets.coffee
+++ scripts/test-instance/parallel-sets.coffee
@@ -72,13 +72,13 @@
   ]

   [
-    { name : 'collaboration collaboration' }
+    # { name : 'collaboration collaboration' }
     { name : 'environments snapshot' }
   ]

-  [
-    { name : 'collaboration start', NIGHTWATCH_OPTIONS: '--env host,participant' }
-  ]
+  # [
+  #   { name : 'collaboration start', NIGHTWATCH_OPTIONS: '--env host,participant' }
+  # ]

   [
     { name : 'environments vm' }


Exit with 0 when process exists

Exit with 0 when process exists

--- src/atom-application.coffee
+++ src/atom-application.coffee
@@ -32,7 +32,7 @@
     @windows = []

     @sendArgumentsToExistingProcess (success) =>
-      process.exit(1) if success # An Atom already exists, kill this process
+      process.exit(0) if success # An Atom already exists, kill this process
       @listenForArgumentsFromNewProcess()
       @setupNodePath()
       @setupJavaScriptArguments()


Added gulp.task and dependencies to client-scripts

--- gulp/client-scripts.coffee
+++ gulp/client-scripts.coffee
@@ -12,4 +12,20 @@
 # together with all necessary libraries and frameworks
 #
 # rosenbea
-#+#
+
+module.exports = (gulp, config) ->
+
+  gulp.task 'client:scripts', ->
+
+    # add the necessary dependencies
+    path = require 'path'
+    lint = require 'gulp-coffeelint'
+    addsrc = require 'gulp-add-src'
+    gulpif = require 'gulp-if'
+    concat = require 'gulp-concat'
+    coffee = require 'gulp-coffee'
+    uglify = require 'gulp-uglify'
+    plumber = require 'gulp-plumber'
+    sourcemaps = require 'gulp-sourcemaps'
+    ngAnnotate = require 'gulp-ng-annotate'


Parse existing `begin` commands too.

Parse existing `begin` commands too.

--- services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/SnippetManager.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/SnippetManager.coffee
@@ -109,14 +109,28 @@
 				return names
 		return names

+	BEGIN_COMMAND_REGEX = /^\\begin{(\w+)}.*$/gm
+
+	parseBeginCommandNames = (text) ->
+		names = []
+		iterations = 0
+		while match = BEGIN_COMMAND_REGEX.exec(text)
+			names.push match[1]
+			iterations += 1
+			if iterations >= 1000
+				return names
+		return names
+
 	class SnippetManager
 		getCompletions: (editor, session, pos, prefix, callback) ->
 			# console.log ">> get snippet completions", editor, session, pos, prefix
 			docText = session.getValue()
 			customEnvironmentNames = parseCustomEnvironmentNames(docText)
+			beginCommandNames = parseBeginCommandNames(docText)
 			# console.log customEnvironmentNames
+			parsedNames = _.union(customEnvironmentNames, beginCommandNames)
 			snippets = staticSnippets.concat(
-				customEnvironmentNames.map (name) ->
+				parsedNames.map (name) ->
 					{
 						caption: "\\begin{#{name}}..."
 						snippet: """
@@ -125,7 +139,6 @@
 							\\end{#{name}}
 						"""
 						meta: "env"
-
 					}
 			)
 			callback null, snippets


Fix unknown props on croppedImage svg

Fix unknown props on croppedImage svg

--- app/components/cropped-image.cjsx
+++ app/components/cropped-image.cjsx
@@ -40,7 +40,7 @@
     x = (@state.naturalWidth - width) / 2
     y = (@state.naturalHeight - height) / 2

-    <svg viewBox="#{x} #{y} #{width} #{height}" {...@props}>
+    <svg viewBox="#{x} #{y} #{width} #{height}" src={@props.src} width={@props.width} height={@props.height}>
       <image ref="image" x="0" y="0" />
     </svg>


fix: use file id instead of project id

fix: use file id instead of project id

--- app/components/file-header.coffee
+++ app/components/file-header.coffee
@@ -10,11 +10,24 @@

 FileHeaderComponent = Ember.Component.extend

+  roleId: 0
+  userRoles: []
+  globalAlpha:0.4
+  radiusRatio:0.9
+
+  apiScanModal: false
+  isStartingRescan: false
+  dynamicScanModal: false
+  isRequestingManual: false
+  showRemoveRoleConfirmBox: false
+
+  vpnStatuses: ["yes", "no"]
+  loginStatuses: ["yes", "no"]
+  appActions: ["halt", "proceed"]
+  environments: ["staging", "production"]
+
   i18n: Ember.inject.service()
   trial: Ember.inject.service()
-
-  globalAlpha:0.4
-  radiusRatio:0.9

   tStartingScan: t("startingScan")
   tPasswordCopied: t("passwordCopied")
@@ -28,22 +41,9 @@
   tPleaseEnterUserRoles: t("modalCard.manual.pleaseEnterUserRoles")
   tPleaseEnterVPNDetails: t("modalCard.manual.pleaseEnterVPNDetails")

-  dynamicScanModal: false
-  apiScanModal: false
-  isRequestingManual: false
-  isStartingRescan: false
-  showRemoveRoleConfirmBox: false
-
-  roleId: 0
-  userRoles: []
-  environments: ["staging", "production"]
-  appActions: ["halt", "proceed"]
-  loginStatuses: ["yes", "no"]
-  vpnStatuses: ["yes", "no"]
-
   manualscan: (->
-    projectId = @get "file.project.id"
-    @get("store").findRecord "manualscan", projectId
+    fileId = @get "file.id"
+    @get("store").findRecord "manualscan", fileId
   ).property()

   filteredEnvironments: (->
@@ -321,8 +321,8 @@
         additional_comments: additionalComments

       that = @
-      projectId = @get("file.project.id")
-      url = [ENV.endpoints.manualscans, projectId].join '/'
+      fileId = @get("file.id")
+      url = [ENV.endpoints.manualscans, fileId].join '/'
       @get("ajax").put url, data: JSON.stringify(data), contentType: 'application/json'
       .then (result) ->
         that.send "requestManualScan"


Mor remove unused default modifiers

Mor remove unused default modifiers

--- resources/assets/coffee/react/profile-page/cover-selection.coffee
+++ resources/assets/coffee/react/profile-page/cover-selection.coffee
@@ -20,8 +20,6 @@
 bn = 'profile-cover-selection'

 class ProfilePage.CoverSelection extends React.PureComponent
-  @defaultProps = modifiers: []
-
   render: =>
     button
       className: osu.classWithModifiers(bn, @props.modifiers)


Some checks and approve update on JApp.create

 - If app owner publishes an updated version of his app, we flag it as not approved
 - If app.identifier is used before fail with proper err message

--- server/app/bongo/models/app.coffee
+++ server/app/bongo/models/app.coffee
@@ -116,47 +116,51 @@
         data.manifest.authorNick = delegate.getAt 'profile.nickname'
         data.manifest.author = "#{delegate.getAt 'profile.firstName'} #{delegate.getAt 'profile.lastName'}"

-      if app
-        console.log "alreadyPublished trying to update fields"
-
-        if not data.manifest.version
-          callback new KodingError 'Version is not provided.'
+        if app
+          if app.getAt('originId') isnt delegate.getId() and not delegate.can('approve', this)
+            callback new KodingError 'Identifier belongs to different user.'
+          else
+            console.log "alreadyPublished trying to update fields"
+
+            if not data.manifest.version
+              callback new KodingError 'Version is not provided.'
+            else
+              curVersions = app.data.versions
+              versionExists = (curVersions[i] for i in [0..curVersions.length] when curVersions[i] is data.manifest.version).length
+
+            if versionExists
+              callback new KodingError 'Version already exists, update version to publish.'
+            else
+              app.update
+                $set:
+                  title     : data.title
+                  body      : data.body
+                  manifest  : data.manifest
+                  approved  : no # After each update on an app we need to reapprove it
+                $addToSet   :
+                  versions  : data.manifest.version
+              , (err)->
+                if err then callback err
+                else callback null, app
         else
-          curVersions = app.data.versions
-          versionExists = (curVersions[i] for i in [0..curVersions.length] when curVersions[i] is data.manifest.version).length
-
-        if versionExists
-          callback new KodingError 'Version already exists, update version to publish.'
-        else
-          app.update
-            $set:
-              title     : data.title
-              body      : data.body
-              manifest  : data.manifest
-            $addToSet   :
-              versions  : data.manifest.version
-          , (err)->
-            if err then callback err
-            else callback null, app
-      else
-        app = new JApp
-          title       : data.title
-          body        : data.body
-          manifest    : data.manifest
-          originId    : delegate.getId()
-          originType  : delegate.constructor.name
-          identifier  : data.identifier
-          versions    : [data.manifest.version]
-
-        app.save (err)->
-          if err
-            callback err
-          else
-            app.addCreator delegate, (err)->
-              if err
-                callback err
-              else
-                callback null, app
+          app = new JApp
+            title       : data.title
+            body        : data.body
+            manifest    : data.manifest
+            originId    : delegate.getId()
+            originType  : delegate.constructor.name
+            identifier  : data.identifier
+            versions    : [data.manifest.version]
+
+          app.save (err)->
+            if err
+              callback err
+            else
+              app.addCreator delegate, (err)->
+                if err
+                  callback err
+                else
+                  callback null, app

   install: secure ({connection}, callback)->
     {delegate} = connection


add better loging about why project servers are restarted, since there are some problematic cases still

--- salvus/hub.coffee
+++ salvus/hub.coffee
@@ -2544,7 +2544,7 @@
                 cb(false, @)

     restart: (cb) =>
-        winston.debug("restarting a local hub")
+        winston.debug("restarting a local hub -- #{@username}@#{@host}")
         if @_restart_lock
             winston.debug("local hub restart -- hit a lock")
             cb("already restarting")
@@ -2592,8 +2592,6 @@
                         cb(err)
                 # MUST be here, since _restart_lock prevents _exec_on_local_hub!
                 @_restart_lock = true
-
-
         ], (err) =>
             winston.debug("local_hub restart: #{err}")
             @_restart_lock = false
@@ -3119,7 +3117,7 @@
                 cb(err, status)

     _restart_local_hub_daemons: (cb) =>
-        winston.debug("restarting local_hub daemons")
+        winston.debug("restarting local_hub daemons -- #{@username}@#{@host}")
         @_exec_on_local_hub
             command : "restart_smc --timeout=#{@timeout}"
             timeout : 30
@@ -3144,7 +3142,9 @@
         if @_status.local_hub and @_status.sage_server and @_status.console_server
             cb()
         else
-            # Not all daemons are running -- restart required
+            # TODO: it would be better just to force start only the daemons that need to be started -- doing this is
+            # just a first brutal minimum way to do this!
+            winston.debug("Not all daemons are running -- restart required -- #{@username}@#{@host}")
             @_restart_local_hub_daemons (err) =>
                 if err
                     cb(err)


fix a potentially subtle race condition with changefeeds (which matters a lot later when we use them for sync)

--- salvus/hub.coffee
+++ salvus/hub.coffee
@@ -3910,6 +3910,12 @@
                     C = undefined
                 else
                     winston.debug("primus_server: '#{id}' matches existing Client -- re-using")
+
+                    # In case the connection hadn't been officially ended yet the changefeeds might
+                    # have been left open sending messages that won't get through. So ensure the client
+                    # must recreate them all before continuing.
+                    C.query_cancel_all_changefeeds()
+
                     cookies = new Cookies(conn.request)
                     if C._remember_me_value == cookies.get(program.base_url + 'remember_me')
                         old_id = C.conn.id


Remove Composer#compose (form a).

Remove Composer#compose (form a).

--- src/chaplin/composer.coffee
+++ src/chaplin/composer.coffee
@@ -42,49 +42,38 @@
   # Constructs a composition and composes into the active compositions.
   # This function has several forms as described below:
   #
-  # a) compose('name')
-  #    Retrieves a reference to an active composition.
-  #
-  # b) compose('name', Class[, options])
+  # a) compose('name', Class[, options])
   #    Composes a class object. The options are passed to the class when
   #    an instance is contructed and are further used to test if the
   #    composition should be re-composed.
   #
-  # c) compose('name', function)
+  # b) compose('name', function)
   #    Composes a function that executes in the context of the controller;
   #    do NOT bind the function context.
   #
-  # d) compose('name', options, function)
+  # c) compose('name', options, function)
   #    Composes a function that executes in the context of the controller;
   #    do NOT bind the function context and is passed the options as a
   #    parameter. The options are further used to test if the composition
   #    should be recomposed.
   #
-  # e) compose('name', options)
+  # d) compose('name', options)
   #    Gives control over the composition process; the compose method of
-  #    the options hash is executed in place of the function of form (d) and
+  #    the options hash is executed in place of the function of form (c) and
   #    the check method is called (if present) to determine re-composition (
   #    otherwise this is the same as form [c]).
   #
-  # f) compose('name', CompositionClass[, options])
+  # e) compose('name', CompositionClass[, options])
   #    Gives complete control over the composition process.
   #
   compose: (name, second, third) ->
-    # Retrieve an active composition item if only the name is passed; form (a).
-    if arguments.length is 1
-      composition = @compositions[name]
-      if not composition or composition.stale()
-        return
-      else
-        return composition.item
-
     # Normalize the arguments
-    # If the second parameter is a function we know it is (b) or (c).
+    # If the second parameter is a function we know it is (a) or (b).
     if typeof second is 'function'
-      # This is form (b) or (f) with the optional options hash if the third
+      # This is form (a) or (e) with the optional options hash if the third
       # is an obj or the second parameter's prototype has a dispose method
       if third or second::dispose
-        # If the class is a Composition class then it is form (f).
+        # If the class is a Composition class then it is form (e).
         if second.prototype instanceof Composition
           return @_compose name, composition: second, options: third
         else
@@ -100,14 +89,14 @@
             if disabledAutoRender and typeof @item.render is 'function'
               @item.render()

-      # This is form (c).
+      # This is form (b).
       return @_compose name, compose: second

-    # If the third parameter exists and is a function this is (d).
+    # If the third parameter exists and is a function this is (c).
     if typeof third is 'function'
       return @_compose name, compose: third, options: second

-    # This must be form (e).
+    # This must be form (d).
     return @_compose name, second

   _compose: (name, options) ->
@@ -144,7 +133,9 @@
   # Retrieves an active composition using the compose method and a passed
   # callback.
   retrieve: (name, callback) ->
-    callback @compose name
+    active = @compositions[name]
+    item = (if active and not active.stale() then active.item else undefined)
+    callback item

   # Declare all compositions as stale and remove all that were previously
   # marked stale without being re-composed.


Remove forgotten logging

Remove forgotten logging

--- server/controllers/messages.coffee
+++ server/controllers/messages.coffee
@@ -184,7 +184,6 @@
     # file in the DS, from a previous save of the draft
     # cache it and pass around
     if attachment.url
-        log.debug "D"
         stream = req.message.getBinary filename, (err) ->
             log.error "Attachment streaming error", err if err

@@ -195,7 +194,6 @@
     # file just uploaded, take the buffer from the multipart req
     # content is a buffer
     else if req.files[filename]
-        log.debug "E"
         callback null, req.files[filename].content

     else
@@ -227,9 +225,7 @@
     steps.push (cb) ->
         log.debug "gathering attachments"
         async.mapSeries message.attachments, (attachment, cbMap) ->
-            log.debug "A"
             contentToBuffer req, attachment, (err, content) ->
-                log.debug "B"
                 return cbMap err if err
                 return cbMap null,
                     content            : content
@@ -239,7 +235,6 @@
                     contentDisposition : attachment.contentDispositioncontentToBuffer

         , (err, cacheds) ->
-            log.debug "C"
             return cb err if err
             message.attachments = cacheds
             cb()


Use only the first encountered prefix for a URI

--- src/index.coffee
+++ src/index.coffee
@@ -56,7 +56,8 @@
 		ctx = self.loadContext(ctx)
 		revCtx = {}
 		for prefix, uri of ctx
-			revCtx[uri] = prefix
+			if uri not of revCtx
+				revCtx[uri] = prefix
 		return {
 			shorten: (uri) ->
 				[base, term] = self.destructureURI uri
@@ -68,6 +69,8 @@
 				if prefix of ctx
 					curie = ctx[prefix] + term
 				return curie
+			shorten_expand: (str) ->
+				return @shorten @expand str
 		}

 	destructureCURIE : (curie) ->


fix master's lack of pr number

fix master's lack of pr number

--- src/index.coffee
+++ src/index.coffee
@@ -52,7 +52,8 @@
             throw new Error("Missing required environment variables:\n\n#{missing.join('\n')}\n")

         ENV.commitMessage = exec('git --no-pager log --pretty=format:"%s" -1').replace(/\\"/g, '\\\\"')
-        ENV.prNumber = basename(process.env['CI_PULL_REQUEST'])
+        # will either be a PR with a number or just a commit
+        ENV.prNumber = if process.env['CI_PULL_REQUEST'] then basename(process.env['CI_PULL_REQUEST']) else null
         ENV.githubDomain  = options.githubDomain ? 'api.github.com'
         return new Bot(ENV)


Clean up OL view model

Clean up OL view model

--- services/web/app/coffee/Features/Project/ProjectController.coffee
+++ services/web/app/coffee/Features/Project/ProjectController.coffee
@@ -442,10 +442,8 @@
 			id: project.id
 			name: project.title
 			lastUpdated: moment.unix(project.updated_at)
-#			publicAccessLevel:
-#			accessLevel:
+			accessLevel: "readOnly",
 			archived: project.removed || project.archived
-#			owner_ref:
 			isOLProject: true
 		}


Use the configured default extension

Use the configured default extension

Right now the code does not take defaultExtension into account.

--- src/index.coffee
+++ src/index.coffee
@@ -16,6 +16,8 @@
       @root = sysPath.join 'app', @config.files.templates.root, sysPath.sep
     if @config.modules.wrapper is off
       @modulesPrefix = ''
+    if @config.files.templates.defaultExtension?
+      @extension = @config.files.templates.defaultExtension
     null

   compile: (data, path, callback) ->


Input choice as editor and password match check

--- src/index.coffee
+++ src/index.coffee
@@ -21,14 +21,32 @@
     message: 'Operation'
     choices: ['Encrypt', 'Decrypt']
   3:
+    name: 'use_editor'
+    type: 'confirm'
+    default: false
+    message: 'Use editor for input?'
+  4:
+    name: 'input'
+    type: 'editor'
+    message: 'Input'
+    when: (answers) -> answers.use_editor
+    validate: (input) -> input isnt ''
+  5:
     name: 'input'
     type: 'input'
     message: 'Input'
+    when: (answers) -> not answers.use_editor
     validate: (input) -> input isnt ''
-  4:
-    name: 'pass'
-    type: 'input'
+  6:
+    name: 'pass_first'
+    type: 'password'
     message: 'Passphrase'
+    validate: (input) -> input isnt ''
+  7:
+    name: 'pass_second'
+    type: 'password'
+    message: 'Passphrase (again)'
+    when: (answers) -> answers.operation is 'Encrypt'
     validate: (input) -> input isnt ''

@@ -53,6 +71,11 @@

 module.exports = ->
   require('inquirer').prompt((v for k,v of questions)).then((a) ->
-    output = engines[a.method][a.operation](a.input, a.pass)
+
+    if a.operation is 'Encrypt' and a.pass_first isnt a.pass_second
+      console.log("Error: The supplied passphrases do not match")
+      return
+
+    output = engines[a.method][a.operation](a.input, a.pass_first)
     console.log("\n" + output + "\n")
   )


Workaround: expiry not formatted on android (#123)

* Workaround: expiry not formatted on android

* Fixed for failing testcase

--- src/index.coffee
+++ src/index.coffee
@@ -180,13 +180,18 @@

 formatCardNumber = (maxLength) -> (e) ->
   # Only format if input is a number
-  digit = String.fromCharCode(e.which)
-  return unless /^\d+$/.test(digit)
-
-  target = e.target
-  value   = QJ.val(target)
-  card    = cardFromNumber(value + digit)
-  length  = (value.replace(/\D/g, '') + digit).length
+  if e.which > 0
+    digit = String.fromCharCode(e.which);
+    value = QJ.val(e.target) + digit
+  else # android keycode not provided workaround
+    digit = e.data;
+    value = QJ.val(e.target)
+
+  return unless /^\d+$/.test(digit)
+
+  target = e.target
+  card    = cardFromNumber(value)
+  length  = (value.replace(/\D/g, '')).length

   upperLengths = [16]
   upperLengths = card.length if card
@@ -207,6 +212,7 @@
     re = /(?:^|\s)(\d{4})$/

   # If '4242' + 4
+  value = value.substring(0, value.length - 1)
   if re.test(value)
     e.preventDefault()
     QJ.val(target, value + ' ' + digit)
@@ -237,14 +243,18 @@
 # Format Expiry

 formatExpiry = (e) ->
+  target = e.target
   # Only format if input is a number
-  digit = String.fromCharCode(e.which)
-  return unless /^\d+$/.test(digit)
-
-  target = e.target
-  val     = QJ.val(target) + digit
-
-  if /^\d$/.test(val) and val not in ['0', '1']
+  if e.which > 0
+    digit = String.fromCharCode(e.which);
+    val = QJ.val(target) + digit
+  else # android keycode not provided workaround
+    digit = e.data;
+    val = QJ.val(target)
+
+  return unless /^\d+$/.test(digit)
+
+  if /^\d$/.test(val) and val not in ['0', '1']
     e.preventDefault()
     QJ.val(target, "0#{val} / ")
     QJ.trigger(target, 'change')
@@ -534,7 +544,7 @@
     QJ.on el, 'keydown', formatBackCardNumber
     QJ.on el, 'keyup blur', setCardType
     QJ.on el, 'paste', reFormatCardNumber
-    QJ.on el, 'input', reFormatCardNumber
+    QJ.on el, 'input', formatCardNumber(maxLength)
     el
   @getCardArray: -> return cards
   @setCardArray: (cardArray) ->


Don't support using the ol.children as a parent or sibling in the API

--- src/index.coffee
+++ src/index.coffee
@@ -29,11 +29,8 @@
     throw new Error 'Invalid type' if type not in [ 'item', 'group' ]

     if parentGroupElement?
-      throw new Error 'Invalid parent group' if parentGroupElement.tagName not in [ 'LI', 'OL' ]
-
-      if parentGroupElement.tagName == 'LI'
-        parentGroupElement = parentGroupElement.nextSibling
-        throw new Error 'Invalid parent group' if parentGroupElement?.tagName != 'OL'
+      throw new Error 'Invalid parent group' if parentGroupElement.tagName != 'LI' or ! parentGroupElement.classList.contains 'group'
+      parentGroupElement = parentGroupElement.nextSibling
     else
       parentGroupElement = @treeRoot

@@ -56,8 +53,7 @@
   insertBefore: (element, type, referenceElement) ->
     throw new Error 'Invalid type' if type not in [ 'item', 'group' ]
     throw new Error 'A reference element is required' if ! referenceElement?
-    throw new Error 'Invalid reference element' if referenceElement.tagName not in [ 'LI', 'OL' ]
-    referenceElement = referenceElement.nextSibling if referenceElement.classList.contains 'group'
+    throw new Error 'Invalid reference element' if referenceElement.tagName != 'LI'

     element.classList.add type
     element.draggable = true


Root can be absolute path on windows

Root can be absolute path on windows

--- src/index.coffee
+++ src/index.coffee
@@ -20,7 +20,7 @@

   findRoot: ->
     @root = if @program.root then @program.root else 'end2endTests'
-    @root = do process.cwd + '/' + @root if @root.indexOf '/' isnt -1
+    @root = do process.cwd + '/' + @root if @root[0] isnt '/' and @root[1] isnt ':'
     @root += '/'

   loadConfig: ->


Load comments in updated submits in downloadSubmits

--- server/cron/downloadSubmits.coffee
+++ server/cron/downloadSubmits.coffee
@@ -55,7 +55,6 @@
         return document.getElementById("source-textarea").innerHTML

     getComments: (runid) ->
-        return []  # temporary
         [contest, run] = @parseRunId(runid)
         data = await @adminUser.download("http://informatics.mccme.ru/py/comment/get/#{contest}/#{run}")
         comments = JSON.parse(data).comments
@@ -85,14 +84,12 @@

         date = new Date(moment(date + "+03"))

-        comments = await @getComments(runid)
         newSubmit = new Submit(
             _id: runid,
             time: date,
             user: uid,
             problem: "p" + pid,
-            outcome: outcome,
-            comment: comments
+            outcome: outcome
         )
         newUser = new User(
             _id: uid,
@@ -109,9 +106,10 @@
             logger.debug "Submit already in the database"
             return res

-        [source, results] = await Promise.all([@getSource(runid), @getResults(runid)])
+        [source, comments, results] = await Promise.all([@getSource(runid), @getComments(runid), @getResults(runid)])
         newSubmit.source = source
         newSubmit.results = results
+        newSubmit.comments = comments

         logger.debug "Adding submit"
         await newSubmit.upsert()


Minify exclusion

Minify exclusion

--- framer/InputLayer.coffee
+++ framer/InputLayer.coffee
@@ -67,8 +67,8 @@
 			# Check last character
 			lastCharacter = @input.value.substr(@input.value.length - 1)

-			# Exclude enter, space and caps lock
-			unless e.which is 20 or e.which is 13 or @value is ""
+			# Exclude enter, shift, caps lock, control, alt, command etc.
+			unless e.which in [13, 16, 17, 18, 20, 91, 93] or @value is ""
 				@emit("change:value", @value)
 				@emit(Events.InputValueChange, @value)


added a z-index

added a z-index

--- src/coffee/ScoreView.coffee
+++ src/coffee/ScoreView.coffee
@@ -21,7 +21,7 @@
     @onLoadData()
     @registerEvent()
     @commentWindowWidth = 500
-    domUtils.css @commentWindowView.el, {position:'absolute', width:"#{@commentWindowWidth}px", display:'none'}
+    domUtils.css @commentWindowView.el, {position:'absolute', width:"#{@commentWindowWidth}px", display:'none',zIndex:1}
     document.addEventListener 'click', => @closeComment()

   ###


another focus bug

another focus bug

--- app/controllers/ToolWindow.coffee
+++ app/controllers/ToolWindow.coffee
@@ -90,14 +90,14 @@
     _.each $(".#{@className}"), (toolWindow) ->
       z_indexes.push $(toolWindow).css 'z-index'

-    _.max z_indexes
+    _.max z_indexes, (num) -> parseInt(num)

   generatePosition: ->
     doc_width = $(document).width()
     doc_height = $(document).height()

-    x_max = doc_width * 0.5
-    x_min = doc_width * 0.08
+    x_max = doc_width * 0.6
+    x_min = doc_width * 0.16

     y_max = doc_height * 0.5
     y_min = doc_height * 0.05


Make CSV downloads have a filename

Make CSV downloads have a filename

--- server/routes/api/indicator.coffee
+++ server/routes/api/indicator.coffee
@@ -72,6 +72,10 @@
           console.error err
           return res.send(500, "Can't retrieve indicator data for #{req.params.id}")

+        res.set('Content-Disposition',
+          "attachment; filename=NRT #{indicator.short_name} Data.csv"
+        )
+
         res.csv(indicatorData)
     )


All-done alert

All-done alert

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -103,6 +103,7 @@

   onNoMoreSubjects: =>
     @el.removeClass 'loading'
+    alert 'All images have been classified!'

   activate: ->
     super


add responsive design to classify page (redirect to verify if mobile detected)

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -51,6 +51,14 @@

   constructor: ->
     super
+
+    # if mobile device detected, go to verify mode
+    if window.matchMedia("(min-device-width: 320px)").matches and window.matchMedia("(max-device-width: 480px)").matches
+      console.log "BLAH"
+      location.hash = "#/verify"
+    else
+      console.log "BLAH2"
+
     window.classifier = @

     @el.find('#star-id').hide()


bugfix: relative path linked resources should resolve relative to final url if request was redirected

Conflicts:
	lib/zombie/window.coffee

--- lib/zombie/window.coffee
+++ lib/zombie/window.coffee
@@ -1,4 +1,4 @@
-#
+#
 createDocument  = require("./document")
 EventSource     = require("eventsource")
 History         = require("./history")
@@ -310,8 +310,6 @@
     if error
       browser.emit("error", error)
     else
-      if url
-        history.updateLocation(window, url)
       browser.emit("loaded", document)

   method = (method || "GET").toUpperCase()
@@ -351,6 +349,15 @@
           return

         window._response = response
+
+        # DDOPSON-2012-11-12 - we do history.replaceEntry here so if we get redirected, the browser's url reflects the last url in the redirection chain
+        # This is important because if we load A which redirects to B, which is a form, and the user submits to C, the 'referer' for the request to C must be B, not A
+        # Lack of fidelity in this behavior would block Facebook OAuth login scenarios
+        # DDOPSON-2012-11-13 - Note that we must also call history.replaceEntry BEFORE processing the document
+        # or else relative path linked assets will be loaded incorrectly for redirected responses
+        # eg www.facebook.com/login.php -> mysite.com, and mysite.com loads /assets/mysite.js.  This would incorrectly resolve to www.facebook.com/assets/mysite.js
+        history.replaceEntry(window, response.url)
+
         # JSDOM fires load event on document but not on window
         windowLoaded = (event)->
           document.removeEventListener("load", windowLoaded)


Don't link deleted user in beatmapset detail

--- resources/assets/coffee/react/_components/beatmapset-mapping.coffee
+++ resources/assets/coffee/react/_components/beatmapset-mapping.coffee
@@ -26,23 +26,23 @@
   render: =>
     user = @props.user ? @props.beatmapset.user

+
     div className: bn,
-      a
-        href: laroute.route 'users.show', user: user.id
-        className: 'avatar avatar--beatmapset'
-        style:
-          backgroundImage: osu.urlPresence(user.avatar_url)
+      if user.id?
+        a
+          href: laroute.route 'users.show', user: user.id
+          className: 'avatar avatar--beatmapset'
+          style:
+            backgroundImage: osu.urlPresence(user.avatar_url)
+      else
+        span className: 'avatar avatar--beatmapset avatar--guest'

       div className: "#{bn}__content",
         div
           className: "#{bn}__mapper"
           dangerouslySetInnerHTML:
             __html: osu.trans 'beatmapsets.show.details.mapped_by',
-              mapper: laroute.link_to_route 'users.show',
-                user.username
-                { user: user.id }
-                class: "#{bn}__user js-usercard"
-                'data-user-id': user.id
+              mapper: @userLink(user)

         @renderDate 'submitted', 'submitted_date'

@@ -60,3 +60,14 @@
         dateTime: @props.beatmapset[attribute]
         title: @props.beatmapset[attribute]
         moment(@props.beatmapset[attribute]).format dateFormat
+
+
+  userLink: (user) ->
+    if user.id?
+      laroute.link_to_route 'users.show',
+        user.username
+        { user: user.id }
+        class: "#{bn}__user js-usercard"
+        'data-user-id': user.id
+    else
+      "#{_.escape(user.username ? osu.trans('users.deleted'))}"


Alias join to flatten.

Alias join to flatten.

--- src/jnoid.coffee
+++ src/jnoid.coffee
@@ -24,6 +24,7 @@
 Jnoid.end = -> new End()

 Jnoid.join = (streamOfStreams)-> streamOfStreams.flatMap(id)
+Jnoid.flatten = Jnoid.join

 class Event
   isEnd: -> false


handle errors normally in addFolder modal

--- services/web/app/coffee/Features/Editor/EditorHttpController.coffee
+++ services/web/app/coffee/Features/Editor/EditorHttpController.coffee
@@ -105,7 +105,7 @@
 			else if error?.message == 'invalid element name'
 				res.status(400).json(req.i18n.translate('invalid_file_name'))
 			else if error?
-				res.status(500).json(req.i18n.translate('generic_something_went_wrong'))
+				next(error)
 			else
 				res.json doc


Fix date locales

Fix date locales

--- app/controllers/tasks.item.coffee
+++ app/controllers/tasks.item.coffee
@@ -149,7 +149,7 @@
   # DATES
   # ----------------------------------------------------------------------------
   datesSave: =>
-    @task.updateAttribute "date", new Date(@date.val()).getTime()
+    @task.updateAttribute "date", @date.datepicker("getDate").getTime()

     # Disables Shizz
     if @date.val().length > 0


removed unnecessary callback

removed unnecessary callback

--- src/api/transactions.coffee
+++ src/api/transactions.coffee
@@ -231,67 +231,67 @@
   that = this

   try
-    yield transactions.Transaction.findByIdAndUpdate transactionId, updates, () ->
-      that.body = "Transaction with ID: #{transactionId} successfully updated"
-      that.status = 'ok'
-      logger.info "User #{that.authenticated.email} updated transaction with id #{transactionId}"
-
-      ###
-      # Update transaction metrics
-      ###
-      transactions.Transaction.findById transactionId, (err, doc) ->
-        if updates['$push'].routes?
-          for k, route of updates['$push']
-            do (route) ->
-              if route.metrics?
-                for metric in route.metrics
-                  if metric.type == 'counter'
-                    logger.info "incrementing mediator counter  #{metric.name}"
-                    sdc.increment "#{domain}.channels.#{doc.channelID}.#{route.name}.mediator_metrics.#{metric.name}"
-
-                  if metric.type == 'timer'
-                    logger.info "incrementing mediator timer  #{metric.name}"
-                    sdc.timing "#{domain}.channels.#{doc.channelID}.#{route.name}.mediator_metrics.#{metric.name}", metric.value
-
-                  if metric.type == 'gauge'
-                    logger.info "incrementing mediator gauge  #{metric.name}"
-                    sdc.gauge "#{domain}.channels.#{doc.channelID}.#{route.name}.mediator_metrics.#{metric.name}", metric.value
-
-              for orchestration in route.orchestrations
-                do (orchestration) ->
-                  orchestrationDuration = orchestration.response.timestamp - orchestration.request.timestamp
-                  orchestrationStatus = orchestration.response.status
-                  orchestrationName = orchestration.name
-                  if orchestration.group
-                    orchestrationName = "#{orchestration.group}.#{orchestration.name}" #Namespace it by group
-
-                  ###
-                  # Update timers
-                  ###
-                  logger.info 'updating async route timers'
-                  sdc.timing "#{domain}.channels.#{doc.channelID}.#{route.name}.orchestrations.#{orchestrationName}", orchestrationDuration
-                  sdc.timing "#{domain}.channels.#{doc.channelID}.#{route.name}.orchestrations.#{orchestrationName}.statusCodes.#{orchestrationStatus}" , orchestrationDuration
-
-                  ###
-                  # Update counters
-                  ###
-                  logger.info 'updating async route counters'
-                  sdc.increment "#{domain}.channels.#{doc.channelID}.#{route.name}.orchestrations.#{orchestrationName}"
-                  sdc.increment "#{domain}.channels.#{doc.channelID}.#{route.name}.orchestrations.#{orchestrationName}.statusCodes.#{orchestrationStatus}"
-
-                  if orchestration.metrics?
-                    for metric in orchestration.metrics
-                      if metric.type == 'counter'
-                        logger.info "incrementing #{route.name} orchestration counter #{metric.name}"
-                        sdc.increment "#{domain}.channels.#{doc.channelID}.#{route.name}.orchestrations.#{orchestrationName}.#{metric.name}", metric.value
-
-                      if metric.type == 'timer'
-                        logger.info  "incrementing #{route.name} orchestration timer #{metric.name}"
-                        sdc.timing "#{domain}.channels.#{doc.channelID}.#{route.name}.orchestrations.#{orchestrationName}.#{metric.name}", metric.value
-
-                      if metric.type == 'gauge'
-                        logger.info  "incrementing #{route.name} orchestration gauge #{metric.name}"
-                        sdc.gauge "#{domain}.channels.#{doc.channelID}.#{route.name}.orchestrations.#{orchestrationName}.#{metric.name}", metric.value
+    yield transactions.Transaction.findByIdAndUpdate(transactionId, updates).exec()
+    that.body = "Transaction with ID: #{transactionId} successfully updated"
+    that.status = 'ok'
+    logger.info "User #{that.authenticated.email} updated transaction with id #{transactionId}"
+
+    ###
+    # Update transaction metrics
+    ###
+    transactions.Transaction.findById transactionId, (err, doc) ->
+      if updates['$push'].routes?
+        for k, route of updates['$push']
+          do (route) ->
+            if route.metrics?
+              for metric in route.metrics
+                if metric.type == 'counter'
+                  logger.info "incrementing mediator counter  #{metric.name}"
+                  sdc.increment "#{domain}.channels.#{doc.channelID}.#{route.name}.mediator_metrics.#{metric.name}"
+
+                if metric.type == 'timer'
+                  logger.info "incrementing mediator timer  #{metric.name}"
+                  sdc.timing "#{domain}.channels.#{doc.channelID}.#{route.name}.mediator_metrics.#{metric.name}", metric.value
+
+                if metric.type == 'gauge'
+                  logger.info "incrementing mediator gauge  #{metric.name}"
+                  sdc.gauge "#{domain}.channels.#{doc.channelID}.#{route.name}.mediator_metrics.#{metric.name}", metric.value
+
+            for orchestration in route.orchestrations
+              do (orchestration) ->
+                orchestrationDuration = orchestration.response.timestamp - orchestration.request.timestamp
+                orchestrationStatus = orchestration.response.status
+                orchestrationName = orchestration.name
+                if orchestration.group
+                  orchestrationName = "#{orchestration.group}.#{orchestration.name}" #Namespace it by group
+
+                ###
+                # Update timers
+                ###
+                logger.info 'updating async route timers'
+                sdc.timing "#{domain}.channels.#{doc.channelID}.#{route.name}.orchestrations.#{orchestrationName}", orchestrationDuration
+                sdc.timing "#{domain}.channels.#{doc.channelID}.#{route.name}.orchestrations.#{orchestrationName}.statusCodes.#{orchestrationStatus}" , orchestrationDuration
+
+                ###
+                # Update counters
+                ###
+                logger.info 'updating async route counters'
+                sdc.increment "#{domain}.channels.#{doc.channelID}.#{route.name}.orchestrations.#{orchestrationName}"
+                sdc.increment "#{domain}.channels.#{doc.channelID}.#{route.name}.orchestrations.#{orchestrationName}.statusCodes.#{orchestrationStatus}"
+
+                if orchestration.metrics?
+                  for metric in orchestration.metrics
+                    if metric.type == 'counter'
+                      logger.info "incrementing #{route.name} orchestration counter #{metric.name}"
+                      sdc.increment "#{domain}.channels.#{doc.channelID}.#{route.name}.orchestrations.#{orchestrationName}.#{metric.name}", metric.value
+
+                    if metric.type == 'timer'
+                      logger.info  "incrementing #{route.name} orchestration timer #{metric.name}"
+                      sdc.timing "#{domain}.channels.#{doc.channelID}.#{route.name}.orchestrations.#{orchestrationName}.#{metric.name}", metric.value
+
+                    if metric.type == 'gauge'
+                      logger.info  "incrementing #{route.name} orchestration gauge #{metric.name}"
+                      sdc.gauge "#{domain}.channels.#{doc.channelID}.#{route.name}.orchestrations.#{orchestrationName}.#{metric.name}", metric.value

   catch e
     utils.logAndSetResponse this, 'internal server error', "Could not update transaction via the API: #{e}", 'error'


Fix failing test

Fix failing test

--- services/web/test/unit/coffee/Project/ProjectControllerTests.coffee
+++ services/web/test/unit/coffee/Project/ProjectControllerTests.coffee
@@ -308,6 +308,7 @@

 			it 'should show for paid users', (done) ->
 				@user.features.github = true
+				@user.features.dropbox = true
 				@res.render = (pageName, opts)=>
 					opts.frontChatWidgetRoomId.should.equal @settings.overleaf.front_chat_widget_room_id
 					done()


Missing comment

Missing comment

--- src/object.coffee
+++ src/object.coffee
@@ -20,7 +20,7 @@

 	on: (event, handler) ->
-		throw new Error("Event type missing.") unless event # TODO: Check type?
+		throw new Error("Event name missing.") unless event # TODO: Check type?
 		throw new Error("Event handler missing.") unless handler # TODO: Check type?

 		@events[event] = [] if not @events[event]


:bug: Do some weird things to avoid an error There is __no__ point of having a data event triggered after a close event has been triggered, but that's what happened in bug #8 So this patch thingy fixes it

--- Source/SSH.coffee
+++ Source/SSH.coffee
@@ -39,9 +39,7 @@
         return Reject(Error) if Error
         ToReturn = stdout: [], stderr: []
         Stream.on('close', ->
-          ToReturn.stdout = ToReturn.stdout.join("");
-          ToReturn.stderr = ToReturn.stderr.join("");
-          Resolve ToReturn
+          Resolve stderr: ToReturn.stderr.join(""), stdout: ToReturn.stdout.join("")
         ).on('data', (Data)->
           ToReturn.stdout.push Data.toString()
         ).stderr.on('data', (Data)->


Adds tests

Adds tests

--- components/article/test/templates.coffee
+++ components/article/test/templates.coffee
@@ -62,3 +62,40 @@
       asset: ->
     html.should.containEql 'article-fullscreen-image'

+  it 'renders a TOC', ->
+    html = render('index')
+      article: new Article
+        title: 'hi'
+        sections: [
+          {
+            type: 'toc'
+            links: [ {name: 'Kana Abe', value: 'Kana'}, { name: 'Bob Olsen', value: 'Bob' } ]
+          }
+        ]
+        contributing_authors: []
+      footerArticles: new Articles
+      crop: (url) -> url
+      resize: (u) -> u
+      moment: moment
+      sd: {}
+      asset: ->
+    html.should.containEql 'Kana Abe'
+
+  it 'does not render a TOC header if there are no links', ->
+    html = render('index')
+      article: new Article
+        title: 'hi'
+        sections: [
+          {
+            type: 'toc'
+            links: []
+          }
+        ]
+        contributing_authors: []
+      footerArticles: new Articles
+      crop: (url) -> url
+      resize: (u) -> u
+      moment: moment
+      sd: {}
+      asset: ->
+    html.should.not.containEql 'Table Of Contents'


Add white list to Rails example config

Add white list to Rails example config

	modified:   examples/rails.coffee

--- examples/rails.coffee
+++ examples/rails.coffee
@@ -17,7 +17,9 @@
   # Establish a white list of URL Strings of the paths to cache.
   # If an incoming request URL does not match an include pattern, it will not
   # be cached and will be simply reverse proxied to the server.
-  config.include /\.js$/
+  config.include /\.js\?body=1$/
+  config.include /\.css\?body=1$/
+  config.include /\.(jpg|jpeg|gif|png|svg)$/i

   return


Changed testpie colors

Changed testpie colors


Fix an issue where the prompt was displaying properly at load if initial code was not given

--- app/javascripts/cs_console.coffee
+++ app/javascripts/cs_console.coffee
@@ -191,6 +191,8 @@
         @console.getInputField().focus()
       ), 10

+    @moveInputForward()
+

   # This method is called whenever a key event is fired within code mirror.
   # This will ensure that the cursor is only typing into the bottom most line


fixed broken error throw in parser

fixed broken error throw in parser

--- src/parser.coffee
+++ src/parser.coffee
@@ -360,7 +360,7 @@
       else if end is '"' and prev is '#' and letter is '{'
         stack.push end = '}'
       prev = letter
-    @error "missing #{ stack.pop() }, starting"
+    throwSyntaxError "missing #{ stack.pop() }, starting"

 # JSX tag matching regexes


Partition example

Partition example

--- src/array_extensions.coffee
+++ src/array_extensions.coffee
@@ -441,6 +441,17 @@
 Partitions the elements into two groups: those for which the iterator returns
 true, and those for which it returns false.

+

+[evens, odds] = [1, 2, 3, 4].partition (n) ->
+  n % 2 == 0
+
+evens
+# => [2, 4]
+
+odds
+# => [1, 3]
+

+
 @name partition
 @methodOf Array#
 @param {Function} iterator


change rabbitmq and ldap conf for vagrant installation

--- config/kite.sharedHosting.vagrant.coffee
+++ config/kite.sharedHosting.vagrant.coffee
@@ -8,7 +8,7 @@
   pidPath               : "/var/run/node/SharedHosting.pid"
   logFile               : "/var/log/node/SharedHosting.log"
   amqp                  :
-    host                : 'rabbitmq.local'
+    host                : 'localhost'
     login               : 'prod-sharedhosting-kite'
     password            : 'djfjfhgh4455__5'
     heartbeat           : 10
@@ -42,9 +42,9 @@
     configFilePath      : '/opt/lsws/conf/httpd_config.xml'
     minRestartInterval  : '10000' # 10 sec
   ldap                  :
-    ldapUrl             : 'ldap://ldap.local'
-    rootUser            : "cn=directory manager"
-    rootPass            : 'Secret123'
+    ldapUrl             : 'ldap://ldap0.prod.system.aws.koding.com'
+    rootUser            : "uid=KAdmin,ou=Special Users,dc=koding,dc=com"
+    rootPass            : 'sOg4:L]iM7!_UV-H'
     groupDN             : 'ou=Beta,ou=Groups,dc=koding,dc=com'
     userDN              : 'ou=Beta,ou=People,dc=koding,dc=com'
     freeUID             : 'uid=betaUsersIDs,dc=koding,dc=com' # special record for next free uid, increments each time when create new user


disable unnecessary test

disable unnecessary test

--- spec/javascripts/jax/material/shader_variable_map_spec.js.coffee
+++ spec/javascripts/jax/material/shader_variable_map_spec.js.coffee
@@ -6,7 +6,7 @@
     map.set a: 1, b: 2
     expect(map.assigns).toEqual {_a: 1, _b: 2}

-  it "should raise an error if assigning a variable that does not exist", ->
+  xit "should raise an error if assigning a variable that does not exist", ->
     expect(-> map.set c: 2).toThrow("Variable 'c' not found!")

   it "should not raise an error if assigning to a variable's real name", ->


Adds semi-working draft * Google API auth and some calls now working * Adds tiny Sinatra server so that OAuth2 callback can be caught

--- interview-scheduler.coffee
+++ interview-scheduler.coffee
@@ -1,203 +0,0 @@
-# Oh, shit. We got some CoffeeScript up in here.
-
-# General idea as follows:
-#
-# 1. Get a list of emails (likely like 5) and ask Google Calendar for those peoples’ busy/free times.
-#
-# 2. Figure out when all of those people can meet over the next week based on their busy/free times.
-#
-# 3. Print the possible schedules.
-
-emails = ['willem.vanbergen@jadedpixel.com', 'aaron.olson@shopify.com', 'cody@shopify.com', 'florian.weingarten@shopify.com']
-
-# Start by just getting the busy/free times
-
-function post(url, body) {
-  # something jquery?
-}
-
-apiKey = "whatever"
-
-body = {
-  "timeMin":"2013-11-30T00:00:00-05:00",
-  "timeMax":"2013-12-07T00:00:00-05:00",
-  "items": [
-    {"id":"willem.vanbergen@jadedpixel.com"},
-    {"id":"justin.mutter@jadedpixel.com"}
-  ],
-  "timeZone":"-05:00"
-}
-
-results = post("https://www.googleapis.com/calendar/v3/freeBusy?key=" + apiKey, body)
-
-# What post results actually look like:
-results = {
-  "kind": "calendar#freeBusy",
-  "timeMin": "2013-11-30T05:00:00.000Z",
-  "timeMax": "2013-12-07T05:00:00.000Z",
-  "calendars": {
-    "willem.vanbergen@jadedpixel.com": {
-      "busy": [
-        {
-          "start": "2013-12-02T15:00:00Z",
-          "end": "2013-12-02T15:15:00Z"
-        },
-        {
-          "start": "2013-12-02T15:30:00Z",
-          "end": "2013-12-02T16:00:00Z"
-        },
-        {
-          "start": "2013-12-02T18:00:00Z",
-          "end": "2013-12-02T18:50:00Z"
-        },
-        {
-          "start": "2013-12-02T21:00:00Z",
-          "end": "2013-12-02T22:00:00Z"
-        },
-        {
-          "start": "2013-12-04T15:00:00Z",
-          "end": "2013-12-04T16:00:00Z"
-        }
-      ]
-    },
-    "justin.mutter@jadedpixel.com": {
-      "busy": [
-        {
-          "start": "2013-12-02T15:30:00Z",
-          "end": "2013-12-02T17:00:00Z"
-        },
-        {
-          "start": "2013-12-02T18:00:00Z",
-          "end": "2013-12-02T19:00:00Z"
-        },
-        {
-          "start": "2013-12-03T15:30:00Z",
-          "end": "2013-12-03T16:15:00Z"
-        },
-        {
-          "start": "2013-12-03T16:30:00Z",
-          "end": "2013-12-03T16:45:00Z"
-        },
-        {
-          "start": "2013-12-04T15:30:00Z",
-          "end": "2013-12-04T16:15:00Z"
-        },
-        {
-          "start": "2013-12-04T16:30:00Z",
-          "end": "2013-12-04T16:45:00Z"
-        },
-        {
-          "start": "2013-12-04T20:00:00Z",
-          "end": "2013-12-04T21:00:00Z"
-        },
-        {
-          "start": "2013-12-05T15:30:00Z",
-          "end": "2013-12-05T16:15:00Z"
-        },
-        {
-          "start": "2013-12-05T16:30:00Z",
-          "end": "2013-12-05T16:45:00Z"
-        },
-        {
-          "start": "2013-12-05T18:30:00Z",
-          "end": "2013-12-05T19:00:00Z"
-        },
-        {
-          "start": "2013-12-06T15:30:00Z",
-          "end": "2013-12-06T16:15:00Z"
-        },
-        {
-          "start": "2013-12-06T16:30:00Z",
-          "end": "2013-12-06T16:45:00Z"
-        },
-        {
-          "start": "2013-12-06T19:00:00Z",
-          "end": "2013-12-06T19:30:00Z"
-        }
-      ]
-    }
-  }
-}
-
-# Ok, so now how do I figure out the times? Algorithms, man.
-
-# Use a big array?
-
-# start with a function that tells me if one person is free at a time.
-isFreeAtATime = (busyTimes, time) ->
-  # The idea is that I perform a Date.parse to get
-
-freeSlots = (busyTimes) ->
-  # returns an array of times between 10am and 5pm (7 slots) indicating free/busy
-
-allSlots = (emails) ->
-  # returns an array of free/busy arrays
-
-countAvailableSlots = (freeBusySlots) ->
-  # returns number of available slots
-
-# maybe keep freeBusySlots as an object that also links to names
-
-slotsThatWork = (day, interviewers) ->
-  interviewers
-  workingSlots
-
-  # Go through each hour of the interviewing day and see if the currently inspected slot works for any interviewers
-
-  timeframe.each {|timeframe|
-    interviewers.each {|interviewer|
-      if doesTimeframeWork(interviewer, timeframe) {
-        workingSlots << [timeframe, interviewer]
-        removeInterviewer(interviewer)
-        break
-      }
-    }
-  }
-
-  return workingslots
-  # returns the slots that work matching with emails
-
-permuteUntilSlotsWork(interviewers)
-  slotsThatWork = []
-  permutations = 1
-  do {
-    interviewers = permuteInterviewers(interviewers, permutations)
-    slotsThatWork = slotsThatWork(day, interviewers)
-  } while slotsThatWork.length == interviewers || permutationsExhausted(interviewers, permutations)
-
-comingWeek = []
-
-for i = 0; i < 7; i++ {
-  comingWeek << today + i.days
-}
-
-class Interviewer
-  email
-  slotsAvailable
-
-interviewers = [...]
-
-comingWeek.each {|day|
-  workingSlots = permuteUntilSlotsWork(day, interviewers)
-  print workingSlots
-}
-
-
-
-Then walk through each hour of the day starting from 10am to see if a person is free at that time. If someone is, then remove them from the list of people you’re trying to fit into a day.
-
-Try all permutations of order of people you have in the list to determine if a day can work, but stop as soon as it does.
-
-Hmm... there must be a better way.
-
-What if I were to represent each person’s schedule as a simplified array of hour blocks of the day, from 10am to 5pm, so 7 blocks. I mark the available slots, and then put the arrays next to each other, and then walk a path through the resulting graph and then back track when I have a problem. (This still feels very costly.)
-
-Is there a way to use another dimension to better optimize the algorithm? Can I somehow stack the time slots on top of each other and see things topographically to see if a combination is even possible at all?
-
-That could be interesting – first start out by looking to see everyone’s schedule for a day to count the free slots of an hour. If there are fewer than the number of interviewers, then reject the day immediately.
-
-Ok, let’s try that approach:
-
-1. Count free slots for each person. Reject if # free slots < # interviewers
-2. Fit the first free
-3. If the first tree fails, permute (or I bet I could just shuffle the interviewers) until it works or I exhaust the possibilities


update local unread state when marking read

--- app/js/models/notification.coffee
+++ app/js/models/notification.coffee
@@ -20,4 +20,5 @@
       repository = app.repositories.get(@get("repository").id)
       repository?.decrement()

+    @set 'unread', false
     @save {unread: false}, {patch: true}


Additional comment

Additional comment

--- resources/assets/coffee/_classes/menu.coffee
+++ resources/assets/coffee/_classes/menu.coffee
@@ -45,6 +45,8 @@

   onDocumentReady: =>
     @refresh()
+    # It seems like jQuery's mouseleave sometimes doesn't trigger on page navigation.
+    # This will re-check whatever the mouse is currently pointing at.
     @setMenu null, => @closestMenuId($(':hover').last())


Fix closure variable binding

Fix closure variable binding

--- javascripts/Actions.coffee
+++ javascripts/Actions.coffee
@@ -5,7 +5,9 @@
   actions = {}

   for actionName of Constants
-    actions[ChangeCase.camelCase(actionName)] = (args)->
-      @dispatch(Constants[actionName], args)
-
+    actions[ChangeCase.camelCase(actionName)] = ((actionName)->
+      (args)->
+        @dispatch(Constants[actionName], args)
+      )(actionName)
+
   actions


make sure that we don't keep erroneous timeouts lying around

and don't add a timeout when one is already running. The one running will always end earlier (since the timeout time is constant), so we don't need the second one

--- app/js/util/attention_span.coffee
+++ app/js/util/attention_span.coffee
@@ -8,27 +8,31 @@
     @options.wait_for_neglection ?= 0

   attend: ->
-    clearTimeout @losing_attention_timeout
-    @gaining_attention_timeout = setTimeout =>
+    @clearTimeout 'losing_attention_timeout'
+    @gaining_attention_timeout ?= setTimeout =>
       @gain_attention()
     , @options.wait_for_attention

   neglect: ->
-    clearTimeout @gaining_attention_timeout
-    @losing_attention_timeout = setTimeout =>
-      @loose_attention()
+    @clearTimeout 'gaining_attention_timeout'
+    @losing_attention_timeout ?= setTimeout =>
+      @lose_attention()
     , @options.wait_for_neglection

   has_attention: -> @_has_attention

-    clearTimeout @gaining_attention_timeout
-    clearTimeout @losing_attention_timeout
   lose_attention: ->
+    @clearTimeout 'gaining_attention_timeout'
+    @clearTimeout 'losing_attention_timeout'
     @_has_attention = false
     @options.lost_attention?()

   gain_attention: ->
-    clearTimeout @gaining_attention_timeout
-    clearTimeout @losing_attention_timeout
+    @clearTimeout 'gaining_attention_timeout'
+    @clearTimeout 'losing_attention_timeout'
     @_has_attention = true
     @options.gained_attention?()
+
+  clearTimeout: (name) ->
+    clearTimeout this[name]
+    delete this[name]


Layer: Fixed maxWidth setting if computedStyle returns "none". Fixes fullscreen Layer.

--- src/base/Layer/Layer.coffee
+++ src/base/Layer/Layer.coffee
@@ -395,7 +395,6 @@
 			height: ""
 			margin: ""
 			minWidth: ""
-			maxWidth: ""
 			maxWidth: ""
 			maxHeight: ""

@@ -874,10 +873,10 @@
 			minWidth: minWidth
 			margin: 0

-		if not dim_layer.computedStyle.maxWidth
+		if not dim_layer.computedStyle.maxWidth or dim_layer.computedStyle.maxWidth == 'none'
 			set_css.maxWidth = vp.width + vp.overlap_width - (vp.layer_pos.left - vp.left)

-		if not dim_layer.computedStyle.maxHeight
+		if not dim_layer.computedStyle.maxHeight or dim_layer.computedStyle.maxHeight == 'none'
 			set_css.maxHeight =  vp.height + vp.overlap_height - (vp.layer_pos.top - vp.top)

 		if placement == "c"


Use the command ivar

Use the command ivar

--- src/buffered-process.coffee
+++ src/buffered-process.coffee
@@ -48,6 +48,7 @@
   constructor: ({command, args, options, stdout, stderr, exit}={}) ->
     @emitter = new Emitter
     options ?= {}
+    @command = command
     # Related to joyent/node#2318
     if process.platform is 'win32'
       # Quote all arguments and escapes inner quotes
@@ -232,7 +233,7 @@
     @emitter.emit 'will-throw-error', {error, handle}

     if error.code is 'ENOENT' and error.syscall.indexOf('spawn') is 0
-      error = new Error("Failed to spawn command `#{command}`. Make sure `#{command}` is installed and on your PATH", error.path)
+      error = new Error("Failed to spawn command `#{@command}`. Make sure `#{@command}` is installed and on your PATH", error.path)
       error.name = 'BufferedProcessError'

     throw error unless handled


coffeelint free tilestage doc

coffeelint free tilestage doc

--- js/msa/stage/tileStage.coffee
+++ js/msa/stage/tileStage.coffee
@@ -293,7 +293,7 @@

       # zoom control
       @ctx.fillStyle = "red"
-      @ctx.globalAlpha = 0.5;
+      @ctx.globalAlpha = 0.5
       @ctx.fillRect @canvas.width - 40, @canvas.height - 35,15,15
       callback = -> alert "hi"
       @events.zoomIn = [@canvas.width - 40, @canvas.height - 35,@canvas.width -


Use el.value as the default for getInputValue() to simplify things.

--- src/rivets.coffee
+++ src/rivets.coffee
@@ -148,8 +148,8 @@
 # Returns the current input value for the specified element.
 getInputValue = (el) ->
   switch el.type
-    when 'text', 'textarea', 'password', 'select-one', 'radio', 'number' then el.value
     when 'checkbox' then el.checked
+    else el.value

 # Returns an event binding routine for the specified event.
 eventBinding = (event) -> (el, bind, unbind) ->


Add support for top-level keypaths.

Add support for top-level keypaths.

--- src/rivets.coffee
+++ src/rivets.coffee
@@ -150,8 +150,12 @@
             path = context.shift()
             splitPath = path.split /\.|:/
             options.formatters = pipes
-            model = @models[splitPath.shift()]
             options.bypass = path.indexOf(':') != -1
+            if splitPath[0]
+              model = @models[splitPath.shift()]
+            else
+              model = @models
+              splitPath.shift()
             keypath = splitPath.join '.'

             if model


Make sure we're not trying to update the nested view in the if binder if it's non-existant at that point.

--- src/rivets.coffee
+++ src/rivets.coffee
@@ -560,7 +560,7 @@
           delete @nested

     update: (models) ->
-      @nested.update models
+      @nested?.update models

   unless:
     block: true


delete menu

delete menu

--- menus/auto-id-class.cson
+++ menus/auto-id-class.cson
@@ -1,22 +0,0 @@
-# See https://atom.io/docs/latest/hacking-atom-package-word-count#menus for more details
-'context-menu':
-  'atom-text-editor': [
-    {
-      'label': 'Toggle auto-id-class'
-      'command': 'auto-id-class:toggle'
-    }
-  ]
-'menu': [
-  {
-    'label': 'Packages'
-    'submenu': [
-      'label': 'auto-id-class'
-      'submenu': [
-        {
-          'label': 'Toggle'
-          'command': 'auto-id-class:toggle'
-        }
-      ]
-    ]
-  }
-]


Suppress 404 console error log

Suppress 404 console error log

--- src/routes.coffee
+++ src/routes.coffee
@@ -47,7 +47,7 @@
   try
     controller = require "./controllers/" + controllerName
   catch e
-    console.warn "controller not found: " + controllerName, e
+#    console.warn "controller not found: " + controllerName, e
     next()
     return
   data = null


Fix case where saving query always used current context/view

--- src/coffee/cilantro/ui/query/dialog.coffee
+++ src/coffee/cilantro/ui/query/dialog.coffee
@@ -105,12 +105,17 @@
                 description: @ui.description.val()
                 usernames_or_emails: @ui.email.val()
                 public: @ui.publicity.prop('checked')
-                context_json: @data.context.toJSON().json
-                view_json: @data.view.toJSON().json

             # Create a new model if not defined
             if not @model
                 @model = new @collection.model
+
+                # We only want to set the context and view json if this is a
+                # new saved query. Otherwise, we would be stomping on the saved
+                # context and view with whatever the user currently has in
+                # their session.
+                attrs.context_json = @data.context.toJSON().json
+                attrs.view_json = @data.view.toJSON().json

             # Don't stomp on model's collection if it already exists in one
             if not @model.collection
@@ -166,4 +171,4 @@
             delete @model
             @$el.modal('hide')

-    { DeleteQueryDialog, EditQueryDialog }+    { DeleteQueryDialog, EditQueryDialog }


More output.

More output.

--- src/coffee/pricesync.coffee
+++ src/coffee/pricesync.coffee
@@ -37,6 +37,10 @@
       @getCustomerGroup(@masterClient, CUSTOMER_GROUP_SALE)
       @getCustomerGroup(@retailerClient, CUSTOMER_GROUP_SALE)
     ]).spread (retailerChannelInMaster, masterCustomerGroup, retailerCustomerGroup) =>
+      console.error "retailerChannelInMaster %j", retailerChannelInMaster
+      console.error "masterCustomerGroup %j", masterCustomerGroup
+      console.error "retailerCustomerGroup %j", retailerCustomerGroup
+
       @getPublishedProducts @retailerClient, ((page, count) -> console.error "Page #{page} processed - #{count} price update(s) done."), (retailerProduct) =>
         current = retailerProduct.masterData.current
         current.variants or= []
@@ -163,7 +167,8 @@
   _updatePrices: (retailerPrices, masterPrices, channelId, variantInMaster, retailerCustomerGroupId, masterCustomerGroupId) ->
     actions = []
     syncAmountOrCreate = (retailerPrice, masterPrice, priceType = 'normal') ->
-      if masterPrice and retailerPrice
+      console.error "Comparing %j", retailerPrice, masterPrices
+      if masterPrice? and retailerPrice?
         if masterPrice.value.currencyCode isnt retailerPrice.value.currencyCode
           console.error "SKU #{variantInMaster.sku}: There are #{priceType} prices with different currencyCodes. R: #{retailerPrice.value.currencyCode} -> M: #{masterPrice.value.currencyCode}"
         else
@@ -175,7 +180,7 @@
               action: 'changePrice'
               variantId: variantInMaster.id
               price: price
-      else if retailerPrice
+      else if retailerPrice?
         # Add new price
         price = _.clone retailerPrice
         # add channel for retailer in master
@@ -212,6 +217,7 @@
       liveAction.staged = false
       actions.push liveAction

+    console.error "ACTIONS %j", actions
     actions

   _normalPrice: (prices) ->


Adiciona ? que estava quebrando tudo

Adiciona ? que estava quebrando tudo

--- src/coffee/vtex-i18n.coffee
+++ src/coffee/vtex-i18n.coffee
@@ -65,7 +65,7 @@
 				return '.'

 	translateHtml: (selector = 'html') =>
-		$(selector).i18n() if window.i18n
+		$(selector).i18n?() if window.i18n

 	###
 	# Caso o callback seja do tipo function, chama a função


Add message on saving display mode

Add message on saving display mode

--- web/volume/spreadsheet.coffee
+++ web/volume/spreadsheet.coffee
@@ -1252,7 +1252,7 @@
         $scope.state =
           name: 'default'
           save: ->
-            return unless @name
+            return unless name = @name
             state =
               key: Key.id
               sort: currentSort && {
@@ -1274,8 +1274,11 @@
             delete state.pivot unless state.pivot
             delete state.public unless state.public

-            router.http(router.controllers.postVolumeState, volume.id, encodeURIComponent(@name), state).then =>
-                volume.state[@name] = state
+            router.http(router.controllers.postVolumeState, volume.id, encodeURIComponent(name), state).then ->
+                volume.state[name] = state
+                messages.add
+                  type: 'green'
+                  body: 'Display mode "' + name + '" saved successfully.'
                 return
               , (res) ->
                 messages.addError


Fixing an issue with node.js server restart.

--- src/server.coffee
+++ src/server.coffee
@@ -22,7 +22,7 @@
 # Files to ignore
 ignored = (file) ->
   filename = sysPath.basename(file)
-  /^\.|~$/.test(filename) or /\.swp/.test(filename) or file.match(project.buildDir)
+  /^\.|~$/.test(filename) or /\.swp/.test(filename) or file.match(project.buildDir) or file.match('node_modules')

 # The `LiveReloadServer` sets up a web socket server to communicate with the browser.
 class LiveReloadServer
@@ -81,7 +81,7 @@
     watcher.on 'error', (error) ->
       logging.error "Error occurred while watching files: #{error}"

-  start: ->
+  start: =>
     child = exports.child
     if child
       child.shouldRestart = true
@@ -92,15 +92,15 @@
       child = exports.child = spawn 'node', ['server/server.js'], {stdio: 'inherit', cwd: process.cwd()}
       child.shouldRestart = false

-      child.stdout.on 'data', (data) ->
-        console.log data.toString()
-        reloadBrowser() if /Quit the server with CONTROL-C/.test(data.toString())
+      # child.stdout.on 'data', (data) ->
+      #   console.log data.toString()
+      #   reloadBrowser() if /Quit the server with CONTROL-C/.test(data.toString())

-      child.stderr.on 'data', (data) ->
-        if data.toString().length > 1
-          logging.error data
+      # child.stderr.on 'data', (data) ->
+      #   if data.toString().length > 1
+      #     logging.error data

-      child.on 'exit', (code) ->
+      child.on 'exit', (code) =>
         exports.child = null
         if child.shouldRestart
           # Restart the server when files change
@@ -121,7 +121,7 @@

   start: ->
     console.log 'Starting Google App Engine development server...'
-    spawn 'dev_appserver.py', ['server'], {stdio: 'inherit'}
+    spawn 'dev_appserver.py', ['--port=4000', project.serverDir], {stdio: 'inherit'}

 # Public interface


Modify CLI to allow for external documentation templates.

--- lib/cli.coffee
+++ lib/cli.coffee
@@ -222,10 +222,13 @@
   # `Project#generate` can take some options, such as which style to use.  Since we're generating
   # differently depending on whether or not github is enabled, let's set those up now:
   # If a style was passed in, but it isn't registered, try loading a module.
-  unless argv.style? and (style = styles[argv.style])?
+  if argv.style
+    stylePath = path.resolve process.cwd(), argv.style
     try
-      style = require(argv.style) require './styles/base'
+      style = require(stylePath) require './styles/base'
     catch error
+      console.error "Can't load style from `#{stylePath}!"
+      throw error

   options =
     indexPageTitle: argv['index-page-title']


temporary client-side theme switching

temporary client-side theme switching

--- client/editor.coffee
+++ client/editor.coffee
@@ -16,6 +16,8 @@
 Template._BootstrapThemer.events
   'change .theme-selector' : (e) ->
     currentTheme.set e.currentTarget.value
+    # TODO remove this
+    BootstrapThemer.enabledTheme.set e.currentTarget.value

   'change .css-box' : (e) ->
     update = {}


Fixed wrong username

Fixed wrong username

--- client/import.coffee
+++ client/import.coffee
@@ -70,6 +70,7 @@
       console.log "Uploading file " + document.name
       uploadFile file, result.publicationId

+

 currentChecksumDocument = null


fix a typo in the spec comments

fix a typo in the spec comments

--- spec/coffeescripts/wow-spec.coffee
+++ spec/coffeescripts/wow-spec.coffee
@@ -116,7 +116,7 @@
       , timeout

     it 'does not tamper with the style attribute', (done) ->
-      # Scroll down so that 150px of #simple-4 becomes visible.
+      # Scroll down so that 150px of #simple-5 becomes visible.
       window.scrollTo 0, $('#simple-5').offset().top - winHeight + 150
       setTimeout ->
         expect $ '#simple-5'


remove excessive debug logging

remove excessive debug logging

--- src/smc-util/synctable.coffee
+++ src/smc-util/synctable.coffee
@@ -1076,18 +1076,13 @@
         return new_val

     close: (fatal) =>
-        dbg = @_client.dbg("close #{@_extra_dbg}")
-        dbg()
         if @_state == 'closed'
-            dbg("already closed")
             # already closed
             return
         # decrement the reference to this synctable
         if global_cache_decref(@)
-            dbg("cached still")
             # close: not zero -- so don't close it yet -- still in use by multiple clients
             return
-        dbg("really closing")
         @_plug.close()
         @_client.removeListener('disconnected', @_disconnected)
         if not fatal


Not enough

Not enough

--- spec/helpers/spec_helper.js.coffee
+++ spec/helpers/spec_helper.js.coffee
@@ -15,8 +15,9 @@

   toHaveContents = (a, b) ->
     if b && b.constructor == Array
-      JSON.stringify a == JSON.stringify b
+      a.length == b.length && JSON.stringify(a) == JSON.stringify(b)
     else if b && b.constructor == Object
+      return false if Object.keys(a).length == Object.keys(b).length
       for key, val of b
         if !a[key]? || !toHaveContents a[key], val
           return false


Update apm path in specs

Update apm path in specs

--- spec/package-generator-spec.coffee
+++ spec/package-generator-spec.coffee
@@ -51,7 +51,7 @@
       packageGeneratorView.trigger "core:confirm"

       expect(apmExecute).toHaveBeenCalled()
-      expect(apmExecute.mostRecentCall.args[0]).toBe 'apm'
+      expect(apmExecute.mostRecentCall.args[0]).toBe atom.packages.getApmPath()
       expect(apmExecute.mostRecentCall.args[1]).toEqual ['init', '--package', "#{path.join(path.dirname(packagePath), "camel-case-is-for-the-birds")}"]

     describe 'when creating a package', ->
@@ -66,7 +66,7 @@
         packageGeneratorView.trigger "core:confirm"

         expect(apmExecute).toHaveBeenCalled()
-        expect(apmExecute.mostRecentCall.args[0]).toBe 'apm'
+        expect(apmExecute.mostRecentCall.args[0]).toBe atom.packages.getApmPath()
         expect(apmExecute.mostRecentCall.args[1]).toEqual ['init', '--package', "#{packagePath}"]

         apmExecute.mostRecentCall.args[2]()
@@ -86,7 +86,7 @@
         packageGeneratorView.trigger "core:confirm"

         expect(apmExecute).toHaveBeenCalled()
-        expect(apmExecute.mostRecentCall.args[0]).toBe 'apm'
+        expect(apmExecute.mostRecentCall.args[0]).toBe atom.packages.getApmPath()
         expect(apmExecute.mostRecentCall.args[1]).toEqual ['init', '--theme', "#{packagePath}"]

         apmExecute.mostRecentCall.args[2]()


Fix the rest of the replacement specs

Fix the rest of the replacement specs

--- spec/project-find-view-spec.coffee
+++ spec/project-find-view-spec.coffee
@@ -933,39 +933,41 @@
         beforeEach ->
           atom.commands.dispatch(projectFindView[0], 'project-find:toggle-regex-option')

+          projectFindView.findEditor.setText('a')
+          atom.commands.dispatch(projectFindView[0], 'project-find:confirm')
+          waitsForPromise -> searchPromise
+
         it "finds the escape char", ->
-          projectFindView.findEditor.setText('a')
           projectFindView.replaceEditor.setText('\\t')
           atom.commands.dispatch(projectFindView[0], 'project-find:replace-all')

-          waitsForPromise ->
-            replacePromise
+          waitsForPromise -> replacePromise

           runs ->
             fileContent = fs.readFileSync(filePath, 'utf8')
             expect(fileContent).toBe("\t\nb\n\t")

         it "doesn't insert a escaped char if there are multiple backslashs in front of the char", ->
-          projectFindView.findEditor.setText('a')
           projectFindView.replaceEditor.setText('\\\\t')
           atom.commands.dispatch(projectFindView[0], 'project-find:replace-all')

-          waitsForPromise ->
-            replacePromise
+          waitsForPromise -> replacePromise

           runs ->
             fileContent = fs.readFileSync(filePath, 'utf8')
             expect(fileContent).toBe("\\t\nb\n\\t")

-
       describe "when regex option is not set", ->
+        beforeEach ->
+          projectFindView.findEditor.setText('a')
+          atom.commands.dispatch(projectFindView[0], 'project-find:confirm')
+          waitsForPromise -> searchPromise
+
         it "finds the escape char", ->
-          projectFindView.findEditor.setText('a')
           projectFindView.replaceEditor.setText('\\t')
           atom.commands.dispatch(projectFindView[0], 'project-find:replace-all')

-          waitsForPromise ->
-            replacePromise
+          waitsForPromise -> replacePromise

           runs ->
             fileContent = fs.readFileSync(filePath, 'utf8')
@@ -1141,9 +1143,13 @@
             expect(sampleCoffeeContent.match(/sunshine/g)).toHaveLength 7

     describe "when there is an error replacing", ->
+      beforeEach ->
+        projectFindView.findEditor.setText('items')
+        atom.commands.dispatch(projectFindView[0], 'project-find:confirm')
+        waitsForPromise -> searchPromise
+
       it "displays the errors in the results pane", ->
         [callback, deferred, called, resultsPaneView, errorList] = []
-        projectFindView.findEditor.setText('items')
         projectFindView.replaceEditor.setText('sunshine')

         spyOn(atom.workspace, 'replace').andCallFake (regex, replacement, paths, fn) ->