test beforeEach and afterEach in nested describe blocks

--- tests/DescribeTest.coffee
+++ tests/DescribeTest.coffee
@@ -68,16 +68,21 @@

 describe 'Running tests from within nested describe blocks', ->

-  sharedData = {}
-
-  beforeEach -> @suite.shared = sharedData
+  ranBefore = false
+
+  beforeEach ->
+    ranBefore = true
+    console.log "[BEFORE EACH] #{@test.test_case.name}"
+
+  afterEach ->
+    console.log "[AFTER EACH] #{@test.test_case.name}"

   it 'runs the first-level tests correctly', ->

     expect(@suite.name).to.equal 'Running tests from within nested describe blocks'
     expect(@test.test_case.shortName).to.equal "runs the first-level tests correctly"
     expect(@test.test_case.name).to.equal "#{@suite.name} - #{@test.test_case.shortName}"
-    expect(@suite.shared).to.equal sharedData
+    expect(ranBefore).to.be.true

   describe 'when nesting', ->

@@ -86,7 +91,7 @@
       expect(@suite.name).to.equal 'Running tests from within nested describe blocks'
       expect(@test.test_case.shortName).to.equal "adds dashes to generate tinytest group paths"
       expect(@test.test_case.name).to.equal "#{@suite.name} - when nesting - #{@test.test_case.shortName}"
-      expect(@suite.shared).to.equal sharedData
+      expect(ranBefore).to.be.true

     it 'runs nested in both environments', ->

@@ -97,7 +102,7 @@
         expect(@suite.name).to.equal 'Running tests from within nested describe blocks'
         expect(@test.test_case.shortName).to.equal "can handle any depth of nesting"
         expect(@test.test_case.name).to.equal "#{@suite.name} - when nesting - server only - #{@test.test_case.shortName}"
-        expect(@suite.shared).to.equal sharedData
+        expect(ranBefore).to.be.true

   describe.client 'multiple nested on same level', ->

@@ -106,14 +111,14 @@
       expect(@suite.name).to.equal 'Running tests from within nested describe blocks'
       expect(@test.test_case.shortName).to.equal "it shows up in the right order"
       expect(@test.test_case.name).to.equal "#{@suite.name} - multiple nested on same level - #{@test.test_case.shortName}"
-      expect(@suite.shared).to.equal sharedData
+      expect(ranBefore).to.be.true

   it.server 'also works after describe blocks', ->

     expect(@suite.name).to.equal 'Running tests from within nested describe blocks'
     expect(@test.test_case.shortName).to.equal "also works after describe blocks"
     expect(@test.test_case.name).to.equal "#{@suite.name} - #{@test.test_case.shortName}"
-    expect(@suite.shared).to.equal sharedData
+    expect(ranBefore).to.be.true

 # --------------------------------------------------------------------------


Add icons to projects listing

Add icons to projects listing

--- projects.cson
+++ projects.cson
@@ -1,6 +1,7 @@
 [
 	{
 		title: ".atom"
+		icon: "atom-icon"
 		paths: [
 			"~/.atom"
 		]
@@ -8,6 +9,7 @@
 	}
 	{
 		title: ".files"
+		icon: "terminal-icon"
 		paths: [
 			"~/.files"
 		]
@@ -15,6 +17,7 @@
 	}
 	{
 		title: "language-roff"
+		icon: "manpage-icon"
 		paths: [
 			"~/Labs/language-roff"
 		]
@@ -22,20 +25,15 @@
 	}
 	{
 		title: "file-icons"
+		icon: "atom-icon"
 		paths: [
 			"~/Labs/file-icons"
 		]
 		devMode: true
 	}
 	{
-		title: "Atom-FS"
-		paths: [
-			"~/Labs/Atom-FS"
-		]
-		devMode: true
-	}
-	{
 		title: "Utils"
+		icon: "icon-tools"
 		paths: [
 			"~/Labs/Utils"
 		]
@@ -43,6 +41,7 @@
 	}
 	{
 		title: "YASR"
+		icon: "emacs-icon"
 		paths: [
 			"~/Labs/YASR"
 		]


Fix classname generation for history rows.

Previously, this would return an array.  Most Javascript implementations
return a comma-separated list when asked to use an array in a string
context.  However, in this case, we want a space-separated list.

--- dashboard/assets/javascripts/controllers.js.coffee
+++ dashboard/assets/javascripts/controllers.js.coffee
@@ -18,11 +18,11 @@
   classNames: (->
     classes = []

-    classes.pushObject('completed') if @get('completed')
+    classes.pushObject('finished') if @get('finished')
     classes.pushObject('aborted') if @get('aborted')

-    classes
-  ).property('aborted', 'completed')
+    classes.join(' ')
+  ).property('aborted', 'finished')

   queuedAtForDisplay: (->
     stored = (@get('queued_at') || 0) * 1000


Don't try to resend an update if the editor is not joined to the project

--- services/web/public/coffee/ide/editor/ShareJsDoc.coffee
+++ services/web/public/coffee/ide/editor/ShareJsDoc.coffee
@@ -112,12 +112,14 @@
 		detachFromAce: () -> @_doc.detach_ace?()

 		INFLIGHT_OP_TIMEOUT: 5000 # Retry sending ops after 5 seconds without an ack
+		WAIT_FOR_CONNECTION_TIMEOUT: 500 # If we're waiting for the project to join, try again in 0.5 seconds
 		_startInflightOpTimeout: (update) ->
 			@_startFatalTimeoutTimer(update)
-			timer = setTimeout () =>
+			retryOp = () =>
 				# Only send the update again if inflightOp is still populated
 				# This can be cleared when hard reloading the document in which
 				# case we don't want to keep trying to send it.
+				sl_console.log "[inflightOpTimeout] Trying op again"
 				if @_doc.inflightOp?
 					# When there is a socket.io disconnect, @_doc.inflightSubmittedIds
 					# is updated with the socket.io client id of the current op in flight
@@ -126,8 +128,18 @@
 					# So we need both depending on whether the op was submitted before
 					# one or more disconnects, or if it was submitted during the current session.
 					update.dupIfSource = [@connection.id, @_doc.inflightSubmittedIds...]
-					@connection.send(update)
-			, @INFLIGHT_OP_TIMEOUT
+
+					# We must be joined to a project for applyOtUpdate to work on the real-time
+					# service, so don't send an op if we're not. Connection state is set to 'ok'
+					# when we've joined the project
+					if @connection.state != "ok"
+						sl_console.log "[inflightOpTimeout] Not connected, retrying in 0.5s"
+						timer = setTimeout retryOp, @WAIT_FOR_CONNECTION_TIMEOUT
+					else
+						sl_console.log "[inflightOpTimeout] Sending"
+						@connection.send(update)
+
+			timer = setTimeout retryOp, @INFLIGHT_OP_TIMEOUT
 			@_doc.inflightCallbacks.push () =>
 				@_clearFatalTimeoutTimer()
 				clearTimeout timer


Fixed bad regression disabling body json schema completely :)

--- src/mixins/validatable-http-message.coffee
+++ src/mixins/validatable-http-message.coffee
@@ -20,7 +20,7 @@
     @lowercaseHeaders()

     @validateHeaders() unless @headers == undefined || @expected.headers == undefined
-    @validateBody() unless @body == undefined || @expected.body == undefined
+    @validateBody() unless @body == undefined || (@expected.body == undefined && @expected.bodySchema == undefined)
     @validateStatusCode() unless @statusCode == undefined
     @validation


XY: Implement Pixie Plate.

XY: Implement Pixie Plate.

--- server/xy/data/items.coffee
+++ server/xy/data/items.coffee
@@ -5,6 +5,7 @@
 makeTypeResistBerry 'Roseli Berry', 'Fairy'
 makeBoostOnTypeItem 'Luminous Moss', 'Water', specialDefense: 1
 makeBoostOnTypeItem 'Snowball', 'Ice', attack: 1
+makePlateItem 'Pixie Plate', 'Fairy'

 makeItem "Weakness Policy", ->
   this::afterBeingHit = (move, user) ->


removing reference hack code

removing reference hack code

--- Backbone.V1.coffee
+++ Backbone.V1.coffee
@@ -71,22 +71,6 @@
           transformedRow[item.attribute] = new item.type(children) if item.isMulti()

       transformedRow
-#
-#    _(rows).each (row) -> _(schema).each (as, column) ->
-
-      #  if(_.isFunction(as)) {
-      #    if(as.prototype instanceof Backbone.Model)
-      #      row[column] = new as(row[column][0])
-      #    else if (as.prototype instanceof Backbone.Collection)
-      #      row[column] = new as(row[column])
-      #    else
-      #      throw "Unknown Model or Collection type"
-      #  } else if(as != column) {
-      #    row[as] = row[column]
-      #    delete row[column]
-      #  }
-
-

   ### Relation Helpers ###


Login: background image address fixed

Login: background image address fixed

--- client/landing/Login/AppView.coffee
+++ client/landing/Login/AppView.coffee
@@ -82,7 +82,7 @@

     super options, data

-    @setCss 'background-image', "url('../images/unsplash/#{backgroundImageNr}.jpg')"
+    @setCss 'background-image', "url('../a/images/unsplash/#{backgroundImageNr}.jpg')"

     @hidden = yes

@@ -348,7 +348,7 @@

   doLogin:(credentials)->
     (KD.getSingleton 'mainController').handleLogin credentials, @bound 'afterLoginCallback'
-
+
   runExternal = (token)->
     KD.getSingleton("kiteController").run
       kiteName        : "externals"


More reliable way to verify element attributes.

--- spec/specHelper.coffee
+++ spec/specHelper.coffee
@@ -20,8 +20,14 @@
     throw new Error "ERROR: '#{actual.text()}' is not equal to '#{expected.text()}'"

   for attribute in expected[0].attributes
-    unless trim(actual.attr attribute.name) == trim(expected.attr attribute.name)
-      throw new Error "ERROR: '#{attribute.name}=\"#{actual.attr(attribute.name)}\"' is not equal to '#{attribute.name}=\"#{expected.attr(attribute.name)}\"'"
+    actualAttr   = actual[0].getAttribute attribute.name
+    throw new Error "ERROR: Missing exptexted attribute '#{attribute.name}'" unless actualAttr?
+
+    actualAttr   = trim actualAttr
+    expectedAttr = trim expected[0].getAttribute attribute.name
+
+    unless actualAttr == expectedAttr
+      throw new Error "ERROR: '#{attribute.name}=\"#{actualAttr}\"' is not equal to '#{expectedAttr}\"'"

   actualChildren   = actual.children()
   expectedChildren = expected.children()


add TODO for the crappy siteId hash usage

--- src/packages/collaboration/lib/session.coffee
+++ src/packages/collaboration/lib/session.coffee
@@ -134,6 +134,7 @@
   setParticipantStates: (participantStates) ->
     @participants = participantStates.map (state) -> new Participant(state)

+  # TODO: move this functionality into the Participant Object
   getClientIdToSiteIdMap: -> @doc.get('collaborationState.clientIdToSiteId')

   subscribe: (name) ->


Change Deps to Tracker

Change Deps to Tracker

--- sample.coffee
+++ sample.coffee
@@ -1,6 +1,6 @@
 ############################################################################
 #     Copyright (C) 2014-2015 by Vaughn Iverson
-#     fileCollection is free software released under the MIT/X11 license.
+#     meteor-file-sample-app is free software released under the MIT/X11 license.
 #     See included LICENSE file for details.
 ############################################################################

@@ -60,7 +60,7 @@

    # Set up an autorun to keep the X-Auth-Token cookie up-to-date and
    # to update the subscription when the userId changes.
-   Deps.autorun () ->
+   Tracker.autorun () ->
       userId = Meteor.userId()
       Meteor.subscribe 'allData', userId
       $.cookie 'X-Auth-Token', Accounts._storedLoginToken()


fbp-protocol: Provided bogus port in runtime:packetsent

--- lib/commandstream.coffee
+++ lib/commandstream.coffee
@@ -505,7 +505,7 @@
     protocol: 'runtime'
     command: 'packetsent'
     payload:
-      #port: 'sssss' # FIXME
+      port: 'sssss' # FIXME
       event: 'data'
       type: 'any'
       graph: graph.name


Check text on the correct element

Check text on the correct element

--- src/scripts/modules/media/body/body.coffee
+++ src/scripts/modules/media/body/body.coffee
@@ -101,7 +101,7 @@
           if href.substr(0, 1) is '#' and $el.data('type') isnt 'footnote-ref'
             $target = $temp.find(href)
             tag = $target?.prop('tagName')?.toLowerCase()
-            if $target.text() is '[link]'
+            if $el.text() is '[link]'
               tag = tag.charAt(0).toUpperCase() + tag.substring(1)
               $el.text("#{tag}") if tag isnt 'undefined'


Only compile .pegjs files in lib/

Only compile .pegjs files in lib/

--- build/Gruntfile.coffee
+++ build/Gruntfile.coffee
@@ -128,7 +128,7 @@
       lessConfig.glob_to_multiple.src.push("#{directory}/**/*.less")
       prebuildLessConfig.src.push("#{directory}/**/*.less") unless theme
       csonConfig.glob_to_multiple.src.push("#{directory}/**/*.cson")
-      pegConfig.glob_to_multiple.src.push("#{directory}/**/*.pegjs")
+      pegConfig.glob_to_multiple.src.push("#{directory}/lib/*.pegjs")

   grunt.initConfig
     pkg: grunt.file.readJSON('package.json')


Enable tests on windows.

Enable tests on windows.

--- build/Gruntfile.coffee
+++ build/Gruntfile.coffee
@@ -290,7 +290,7 @@
   ciTasks.push('mkdeb') if process.platform is 'linux'
   ciTasks.push('codesign:exe') if process.platform is 'win32' and not process.env.CI
   ciTasks.push('create-windows-installer:installer') if process.platform is 'win32'
-  ciTasks.push('test') if process.platform is 'darwin'
+  ciTasks.push('test') if process.platform is 'darwin' or process.platform is 'win32'
   ciTasks.push('codesign:installer') if process.platform is 'win32' and not process.env.CI
   ciTasks.push('codesign:app') if process.platform is 'darwin' and not process.env.CI
   ciTasks.push('publish-build') unless process.env.CI


webapp/account: fixing strategy buttons (some aspects)

--- src/smc-webapp/r_account.cjsx
+++ src/smc-webapp/r_account.cjsx
@@ -54,6 +54,8 @@
 {PROJECT_UPGRADES} = require('smc-util/schema')

 {APIKeySetting} = require('./api-key')
+
+{ LEGACY_SSO } = require("./passport-types")

 log_strategy = (is_anonymous, name) ->
     if is_anonymous
@@ -539,7 +541,7 @@
         if not id
             return
         webapp_client.unlink_passport
-            strategy : strategy
+            strategy : strategy.name
             id       : id
             cb       : (err) ->
                 if err
@@ -575,14 +577,28 @@
                 </ButtonToolbar>
             </Well>

+    render_strategy_icon: (strategy) ->
+        if strategy.icon?
+            style =
+                verticalAlign: 'text-bottom'
+                borderRadius: '50%'
+                width:'20px'
+                height:'auto'
+            return <img src={strategy.icon} style={style} />
+        else if LEGACY_SSO.indexOf(strategy.name) >= 0
+            return <Icon name={strategy.name} />
+        else
+            return undefined
+
     render_strategy: (strategy, strategies) ->
-        if strategy != 'email'
+        sname = strategy.name
+        if sname != 'email'
             <Button
                 disabled={@props.is_anonymous and not @state.terms_checkbox}
-                onClick = {=>@setState(if strategy in strategies then {remove_strategy_button:strategy, add_strategy_link:undefined} else {add_strategy_link:strategy, remove_strategy_button:undefined})}
-                key     = {strategy}
-                bsStyle = {if strategy in strategies then 'info' else 'default'}>
-                <Icon name={strategy} /> {misc.capitalize(strategy)}...
+                onClick = {=>@setState(if sname in strategies then {remove_strategy_button:strategy, add_strategy_link:undefined} else {add_strategy_link:strategy, remove_strategy_button:undefined})}
+                key     = {sname}
+                bsStyle = {if sname in strategies then 'info' else 'default'}>
+                {@render_strategy_icon(strategy)} {strategy.display ? misc.capitalize(sname)}...
             </Button>

     render_sign_out_error: ->


maybe prevent seeing a blank document

maybe prevent seeing a blank document

--- src/smc-webapp/syncdoc.coffee
+++ src/smc-webapp/syncdoc.coffee
@@ -202,6 +202,7 @@
         @editor.show_startup_message("Loading...", 'info')
         @codemirror.setOption('readOnly', true)
         @codemirror1.setOption('readOnly', true)
+        @codemirror.setValue('Loading...')

         if @filename[0] == '/'
             # uses symlink to '/', which is created by start_smc


ListViewController: refactored lazyloader show/hide logic

--- src/components/list/listviewcontroller.coffee
+++ src/components/list/listviewcontroller.coffee
@@ -379,34 +379,39 @@
   LAZY LOADER
   ###

+  createLazyLoader: ->
+
+    {lazyLoaderOptions} = @getOptions()
+
+    lazyLoaderOptions                or= {}
+    lazyLoaderOptions.itemClass      or= KDCustomHTMLView
+    lazyLoaderOptions.partial         ?= ''
+    lazyLoaderOptions.cssClass         = KD.utils.curry 'lazy-loader', lazyLoaderOptions.cssClass
+    lazyLoaderOptions.spinnerOptions or= size : width : 32
+    {itemClass, spinnerOptions}        = lazyLoaderOptions
+    delete lazyLoaderOptions.itemClass
+
+    wrapper = @scrollView or @getView()
+    wrapper.addSubView @lazyLoader = new itemClass lazyLoaderOptions
+    @lazyLoader.addSubView @lazyLoader.spinner = new KDLoaderView spinnerOptions
+
+
   showLazyLoader:(emitWhenReached = yes)->

-    @hideNoItemWidget() if @noItemView and @getOptions().noItemFoundWidget
-    unless @lazyLoader
-      {lazyLoaderOptions} = @getOptions()
-
-      lazyLoaderOptions                or= {}
-      lazyLoaderOptions.itemClass      or= KDCustomHTMLView
-      lazyLoaderOptions.partial         ?= ''
-      lazyLoaderOptions.cssClass         = KD.utils.curry 'lazy-loader', lazyLoaderOptions.cssClass
-      lazyLoaderOptions.spinnerOptions or= size : width : 32
-      {itemClass, spinnerOptions}        = lazyLoaderOptions
-      delete lazyLoaderOptions.itemClass
-
-      wrapper = @scrollView or @getView()
-      wrapper.addSubView @lazyLoader = new itemClass lazyLoaderOptions
-      @lazyLoader.addSubView @lazyLoader.spinner = new KDLoaderView spinnerOptions
-
-      @lazyLoader.spinner.show()
-      @emit 'LazyLoadThresholdReached'  if emitWhenReached
-      KD.utils.defer => @scrollView?.stopScrolling = yes
+    @hideNoItemWidget()  if @noItemView and @getOptions().noItemFoundWidget
+
+    @createLazyLoader()  unless @lazyLoader
+
+    @lazyLoader.show()
+    @lazyLoader.spinner.show()
+    @emit 'LazyLoadThresholdReached'  if emitWhenReached
+    KD.utils.defer => @scrollView?.stopScrolling = yes

   hideLazyLoader:->

     KD.utils.wait 300, => @scrollView?.stopScrolling = no
     @showNoItemWidget() if @noItemView and @getOptions().noItemFoundWidget
-    if @lazyLoader
-      @lazyLoader.spinner.hide()
-      @lazyLoader.destroy()
-      @lazyLoader = null
+
+    @lazyLoader.spinner.hide()
+    @lazyLoader.hide()


allow admin / technical to be unset by admin

--- controllers/people.coffee
+++ controllers/people.coffee
@@ -38,6 +38,9 @@
 app.put '/people/:id', [loadPerson, ensureAuth], (req, res) ->
   unless req.user.admin
     delete req.body[attr] for attr in ['role', 'admin', 'technical']
+  else # FIXME
+    req.body.admin = !!req.body.admin
+    req.body.technical = !!req.body.technical
   _.extend req.person, req.body
   req.person.save (err) ->
     return next err if err && err.name != 'ValidationError'


:art: Make use of oldBufferPosition

:art: Make use of oldBufferPosition

--- lib/editor-linter.coffee
+++ lib/editor-linter.coffee
@@ -8,10 +8,7 @@
     @inProgress = false
     @inProgressFly = false

-    @lineForMessages = 0 # It's used to compare if the user has changed line so we can updateLineMessages
-
     if @editor is atom.workspace.getActiveTextEditor()
-      @lineForMessages = @editor.getCursorBufferPosition()?.row
       @linter.views.updateLineMessages(true)

     @emitter = new Emitter
@@ -21,11 +18,9 @@
       @editor.onDidSave => @lint(false)
     )
     @subscriptions.add(
-      @editor.onDidChangeCursorPosition ({newBufferPosition}) =>
-        lineForMessages = newBufferPosition.row
-        if lineForMessages isnt @lineForMessages
+      @editor.onDidChangeCursorPosition ({oldBufferPosition, newBufferPosition}) =>
+        if newBufferPosition.row isnt oldBufferPosition.row
           @linter.views.updateLineMessages(true)
-          @lineForMessages = lineForMessages
         @linter.views.updateBubble(newBufferPosition)
     )
     @subscriptions.add(


FlashManager::flasher can flash multiple ranges at once

--- lib/flash-manager.coffee
+++ lib/flash-manager.coffee
@@ -1,33 +1,31 @@
 # Refactoring status: 100%
+_ = require 'underscore-plus'

 class FlashManager
   timeoutID: null
-  marker: null
+  markers: null

   constructor: (@vimState) ->
     {@editor} = @vimState

   flash: ({range, klass, timeout}, fn=null) ->
+    range = [range] unless _.isArray(range)
+    return unless range.length
     @reset()
-
-    @marker = @editor.markBufferRange range,
-      invalidate: 'never',
-      persistent: false
-
+    markerOptions = {ivalidate: 'nerver', persistent: false}
+    @markers = (@editor.markBufferRange(r, markerOptions) for r in range)
     fn?()
-
-    @editor.decorateMarker @marker,
-      type: 'highlight'
-      class: klass
+    decorationOptions = {type: 'highlight', class: klass}
+    @editor.decorateMarker(m, decorationOptions) for m in @markers

     @timeoutID = setTimeout  =>
       @reset()
     , timeout

   reset: ->
-    @marker?.destroy()
+    m.destroy() for m in @markers ? []
     clearTimeout @timeoutID
-    {@marker, @timeoutID} = {}
+    {@markers, @timeoutID} = {}

   destroy: ->
     @reset()


Add config option for SSL support

Add config option for SSL support

--- app.coffee
+++ app.coffee
@@ -66,6 +66,7 @@
 config.enableStatistics ?= on
 config.enableHash ?= on
 config.deterministic ?= off
+config.ssl ?= off
 config.i18n ?=
 	locales: [ 'en', 'de' ]
 	directory: "#{__dirname}/locales"
@@ -189,7 +190,7 @@

 app.use (req, res, next) ->
 	res.set 'wcf-update-server-api', '2.0 2.1'
-	res.set 'wcf-update-server-ssl', 'false'
+	res.set 'wcf-update-server-ssl', if config.ssl then "true" else "false"
 	do next

 app.all /^\/(?:list\/([a-z-]{2,})\.xml)?$/, (req, res) ->


Expand definition for function input variables


Don't call through (prevents notice from being sent

--- coffee/test/honeybadger.spec.coffee
+++ coffee/test/honeybadger.spec.coffee
@@ -55,7 +55,7 @@

   describe '.notify', ->
     it 'delivers the notice', ->
-      spyOn(Honeybadger, '_sendRequest').andCallThrough()
+      spyOn(Honeybadger, '_sendRequest')
       notice = null

       Honeybadger.configure


Changed setup of server.

Changed setup of server.

--- server.coffee
+++ server.coffee
@@ -23,13 +23,13 @@

 certsPath = path.resolve '/etc/letsencrypt/live/3rdhand.info'

-app = express()
-app.use favicon './favicon.ico'
-app.use(bodyParser.json())
-app.use bodyParser.urlencoded extended: true
+server = express()
+server.use favicon './favicon.ico'
+server.use(bodyParser.json())
+server.use bodyParser.urlencoded extended: true

 if DEV
-  app.use (req, res, next) ->
+  server.use (req, res, next) ->
     res.header 'Access-Control-Allow-Origin', '*'
     res.header(
       'Access-Control-Allow-Headers'
@@ -37,13 +37,13 @@
     )
     next()

-app.use express.static path.resolve __dirname, 'dist'
-app.set 'views', './views'
-app.set 'view engine', 'jade'
+server.use express.static path.resolve __dirname, 'dist'
+server.set 'views', './views'
+server.set 'view engine', 'jade'

-app.get '/index.atom', (req, res) ->
+server.get '/index.atom', (req, res) ->
   lang = negotiateLang req
-  res.header 'Content-Type', 'application/atom+xml; charset=utf8'
+  res.header 'Content-Type', 'serverlication/atom+xml; charset=utf8'
   { store } = createStore()
   init(store, {}, lang).then ->
     articles = articleSelectors.containerSelector(store.getState()).articles
@@ -60,10 +60,10 @@
       articles: articles
     )

-app.get '/locales/:lang', (req, res) ->
+server.get '/locales/:lang', (req, res) ->
   API.fetchLocaleStrings(req.params.lang).then res.send.bind res

-app.get '/signup', (req, res) ->
+server.get '/signup', (req, res) ->
   DB.isSignupAllowed().then (isAllowed) ->
     if isAllowed
       res.format
@@ -74,7 +74,7 @@
     else
       res.sendStatus 404

-app.post '/signup', (req, res) ->
+server.post '/signup', (req, res) ->
   API.signup req.body
     .then (body) ->
       res.format
@@ -90,10 +90,10 @@
       else if msg == 'repeat password mismatch'
         res.status(400).send message: msg

-app.use '/users', userRouter
-app.use '/', siteRouter
+server.use '/users', userRouter
+server.use '/', siteRouter

-app.use (err, req, res, next) ->
+server.use (err, req, res, next) ->
   msg = err.message
   if msg == 'session timeout' || msg = 'token expired'
     logger.warn msg
@@ -118,9 +118,8 @@
   ca: fs.readFileSync path.join certsPath, 'chain.pem'

 if PROD
-  server = https.createServer credentials, app
-else
-  server = app
+  https.createServer(credentials, server).listen 8443, ->
+    logger.info 'HTTPS server listening on port 8443...'

 server.listen 8081, ->
   logger.info 'Express web server listening on port 8081...'


refactoring

refactoring

--- server.coffee
+++ server.coffee
@@ -11,6 +11,9 @@
 github    = require "octonode"

 GitHubApi = require "github"
+
+
+app = module.exports = express.createServer()

 nconf
@@ -44,7 +47,6 @@
 #         gitty.push __dirname + "/repos/dsad", "origin", "master", (err, data) ->
 #           console.log "xx5", err, data

-app = module.exports = express.createServer()

@@ -122,22 +124,11 @@
   gh = new GitHubApi version: "3.0.0"
   gh.repos.getFromOrg {org: "communities"}, (err, repos) ->
     repos = _.filter repos, (repo) -> repo.name != 'gitcommunities.com'
-    membersFetchFuncs = []
-    topicsFetchFuncs = []
+    communitiesFetchFuncs = []
     _.each repos, (repo) ->
-      repo.created = moment(repo.created_at).fromNow()
-      repo.pushed = moment(repo.pushed_at).fromNow()
-      membersFetchFuncs.push async.apply getMembers, repo.name
-      topicsFetchFuncs.push async.apply getTopics, repo.name
-    async.parallel membersFetchFuncs, (err, results) ->
-      for i in [0...repos.length]
-        repos[i].members = results[i]
-        repos[i].members_count = results[i].length
-      async.parallel topicsFetchFuncs, (err, results) ->
-        for i in [0...repos.length]
-          repos[i].topics = results[i]
-          repos[i].topics_count = results[i].length
-        res.json repos
+      communitiesFetchFuncs.push async.apply getCommunity, repo.name
+    async.parallel communitiesFetchFuncs, (err, repos) ->
+      res.json repos

 app.get "/api/communities/:community", (req, res) ->
   community = req.params.community
@@ -159,7 +150,12 @@
         callback err
         return
       repo.topics = results.topics
-      repo.members = results.members
+      repo.topics_count = results.topics.length
+      repo.members = results.members
+      repo.members_count = results.members.length
+      repo.created = moment(repo.created_at).fromNow()
+      repo.pushed = moment(repo.pushed_at).fromNow()
+
       callback undefined, repo

 getTopics = (community, callback) ->


audio: Receive adts_frame instead of raw_data_block

--- server.coffee
+++ server.coffee
@@ -875,8 +875,7 @@
     console.log "audio object type is changed to #{config.audioObjectType}"
     updateConfig()

-#  rawDataBlock = adtsFrame[7..]
-  rawDataBlock = adtsFrame
+  rawDataBlock = adtsFrame[7..]
   rtmpServer.sendAudioPacket rawDataBlock, pts

   if ++audioSequenceNumber > 65535


Some more logic around connecting /pca request to get_data.

--- start.coffee
+++ start.coffee
@@ -40,7 +40,16 @@
 				if not req.body[required]?
 					return _error_out res, "Required field '#{required}' not specified."

-			#
+			@get_data req.body.filter, req.body.attributes ( err, docs ) ->
+				if err
+					return _error_out res, "Unable to obtain documents containing attributes matching filter: #{err}"
+
+				# Normalize each attribute by subtracting average
+				# and dividing by standard deviation.
+
+				# Create matrix.
+
+				# Pass off to sylvester to pcaProject to 2 dimensions.

 	get_attributes: ( cb ) ->
 		# Helper function so that code makes logical sense
@@ -69,6 +78,7 @@
 		_r = [ ]
 		for data_obj in @options['data']

+			# Skip any document not matching filters..
 			skip = false
 			for key, val of filters
 				if data_obj[key] isnt val
@@ -76,6 +86,8 @@
 			if skip
 				continue

+			# Create a new object that only contains the attributes we
+			# care about. Push that to the return.
 			_o = { }
 			for attr in attributes
 				_o[attr] = data_obj[attr]


exclude autocomplete-active scope for default enter keymap

--- keymaps/narrow.cson
+++ keymaps/narrow.cson
@@ -13,7 +13,6 @@
 # Only on narrow-editor
 # ---------------------------
 'atom-text-editor.narrow.narrow-editor[data-grammar="source narrow"]':
-  'enter': 'core:confirm'
   # 'tab': 'narrow-ui:move-to-prompt-or-selected-item'
   'f5': 'narrow:refresh'
   'ctrl-l': 'narrow:refresh'
@@ -21,6 +20,9 @@
   # Follow find-and-replace's key-setting
   'alt-cmd-w': 'narrow-ui:toggle-search-whole-word'
   'alt-cmd-c': 'narrow-ui:toggle-search-ignore-case'
+
+'atom-text-editor.narrow.narrow-editor:not(.read-only):not(.autocomplete-active)[data-grammar="source narrow"]':
+  'enter': 'core:confirm'

 'atom-text-editor.narrow.narrow-editor[data-grammar="source narrow"]:not(.vim-mode-plus)':
   'ctrl-r': 'narrow-ui:toggle-auto-preview'


Fixed AngularJS routing refactor impact.

--- src/main/coffee/atom/routes.coffee
+++ src/main/coffee/atom/routes.coffee
@@ -16,15 +16,15 @@
       ($routeProvider) ->
         $routeProvider.when '/available',
           templateUrl: 'partials/available.html'
-          controller: 'Controller.AvailableController'
+          controller: 'AvailableController'

         $routeProvider.when '/summary',
           templateUrl: 'partials/summary.html'
-          controller: 'Controller.SummaryController'
+          controller: 'SummaryController'

         $routeProvider.when '/detail',
           templateUrl: 'partials/detail.html'
-          controller: 'Controller.DetailController'
+          controller: 'DetailController'

         $routeProvider.otherwise redirectTo: '/available'
     ]


use AsyncButton so it displays loading state

--- tutor/src/components/task-step/step-with-reading-content.cjsx
+++ tutor/src/components/task-step/step-with-reading-content.cjsx
@@ -2,7 +2,7 @@
 {Button} = require 'react-bootstrap'
 {TaskStepStore} = require '../../flux/task-step'
 {TaskPanelStore} = require '../../flux/task-panel'
-{ArbitraryHtmlAndMath, ChapterSectionMixin} = require 'shared'
+{AsyncButton, ArbitraryHtmlAndMath, ChapterSectionMixin} = require 'shared'
 CourseData = require '../../helpers/course-data'
 {BookContentMixin, LinkContentMixin} = require '../book-content-mixin'
 RelatedContent = require '../related-content'
@@ -28,12 +28,24 @@

   shouldExcludeFrame: ->
     @props.stepType is 'interactive'
+  getInitialState: ->
+    { isContinuing: false }
+
+  onContinue: ->
+    @setState(isContinuing: true)
+    @props.onContinue()

   renderNextStepLink: ->
     return null unless @props.onContinue
-    <Button bsStyle="primary" onClick={@props.onContinue}>
+
+    <AsyncButton
+      bsStyle="primary"
+      isWaiting={@state.isContinuing}
+      waitingText="Loading…"
+      onClick={this.onContinue}
+    >
       Continue
-    </Button>
+    </AsyncButton>

   shouldOpenNewTab: -> true


Fix detection an external pylama on Windows. See #4

--- lib/linter-pylama.coffee
+++ lib/linter-pylama.coffee
@@ -84,12 +84,11 @@
   executionCheckHandler: (error, stdout, stderr) =>
     pylamaVersion = ''
     if not @enabled
-      versionRegEx = /pylama ([\d\.]+)/
+      versionRegEx = /pylama(.exe)? ([\d\.]+)/
       if versionRegEx.test(stderr)
-        pylamaVersion = versionRegEx.exec(stderr)[1]
-      else
-        if versionRegEx.test(stdout)
-          pylamaVersion = versionRegEx.exec(stdout)[1]
+        pylamaVersion = versionRegEx.exec(stderr)[0]
+      else if versionRegEx.test(stdout)
+        pylamaVersion = versionRegEx.exec(stdout)[0]
       if not pylamaVersion
         result = if error? then '#' + error.code + ': ' else ''
         result += 'stdout: ' + stdout if stdout.length > 0
@@ -100,7 +99,7 @@
         Please, check executable path in the linter settings."
         return
       @enabled = true
-    log "Linter-Pylama: found pylama " + pylamaVersion
+    log "Linter-Pylama: found " + pylamaVersion
     do @initCmd

   initPythonPath: =>


Setup PYTHONPATH before linting.

Setup PYTHONPATH before linting.

--- lib/linter-pylama.coffee
+++ lib/linter-pylama.coffee
@@ -62,7 +62,6 @@
     (lintOnFly) =>
       @lintOnFly_ = lintOnFly

-    do @initPythonPath
     do @initPylama

@@ -74,14 +73,14 @@
     return @lintOnFly_

-  initPythonPath: =>
+  initPythonPath: (cwd) ->
     sep = path.delimiter
     pythonPath = if process.env['PYTHONPATH'] then process.env.PYTHONPATH else ''
     pythonPath = pythonPath.split sep
     pythonPath = pythonPath.filter(Boolean)

-    if @cwd and @cwd not in pythonPath
-      pythonPath.push @cwd
+    if cwd and cwd not in pythonPath
+      pythonPath.push cwd

     process_path = process.env.PWD
     if process_path and process_path not in pythonPath
@@ -216,6 +215,7 @@
   lint: (textEditor) =>
     if not @cmd
       return []
+    @initPythonPath path.dirname do textEditor.getPath
     if @lintOnFly_
       return @lintOnFly textEditor
     else


ListView: Fix for tree if "hasChildren" is provided, but no @children.

--- src/elements/ListView/ListViewTreeNode.coffee
+++ src/elements/ListView/ListViewTreeNode.coffee
@@ -42,6 +42,12 @@
 	getColspan: ->
 		@colspan

+	# overwrite with Method
+	getChildren: null
+
+	# overwrite with Method
+	hasChildren: null
+
 	isLeaf: ->
 		leaf = (if @children
 			false
@@ -231,8 +237,9 @@
 		# console.debug "remove from DOM", @getNodeId(), @is_open, @children?.length, remove_self
 		if @is_open
 			@do_open = true
-			for c in @children
-				c.removeFromDOM()
+			if @children
+				for c in @children
+					c.removeFromDOM()
 		else
 			@do_open = false


fix(thread-list): Remove dup accountIds to avoid extra work

--- internal_packages/thread-list/lib/thread-list.cjsx
+++ internal_packages/thread-list/lib/thread-list.cjsx
@@ -107,7 +107,7 @@
     dataSource = ThreadListStore.dataSource()
     if itemThreadId in dataSource.selection.ids()
       dragThreadIds = dataSource.selection.ids()
-      dragAccountIds = _.pluck(dataSource.selection.items(), 'accountId')
+      dragAccountIds = _.uniq(_.pluck(dataSource.selection.items(), 'accountId'))
     else
       dragThreadIds = [itemThreadId]
       dragAccountIds = [dataSource.getById(itemThreadId).accountId]


Remove usage of "classes"

Remove usage of "classes"

Removed "classes" property references on fieldSchema which only ESP used.

--- src/SchemaUtils.coffee
+++ src/SchemaUtils.coffee
@@ -74,10 +74,8 @@
     values = {}
     schema = collection.simpleSchema()
     Collections.forEachFieldSchema schema, (fieldSchema, paramId) ->
-      # Default value is stored in the "classes" object to avoid being used by SimpleSchema.
-      defaultValue = fieldSchema.classes?.ALL?.defaultValue
-      if defaultValue?
-        values[paramId] = defaultValue
+      defaultValue = fieldSchema.defaultValue
+      if defaultValue? then values[paramId] = defaultValue
     SchemaUtils.unflattenParameters(values, false)

   mergeDefaultParameterValues: (model, collection) ->
@@ -129,20 +127,6 @@
         if fieldSchema.optional == false
           hasRequiredField = true
         @autoLabel(fieldSchema, itemId)
-        # If defaultValue is used, put it into "classes" to prevent SimpleSchema from storing this
-        # value in the doc. We want to inherit this value at runtime for all classes, but not
-        # persist it in multiple documents in case we want to change it later in the schema.
-        defaultValue = fieldSchema.defaultValue
-        if defaultValue?
-          classes = fieldSchema.classes ?= {}
-          allClassOptions = classes.ALL ?= {}
-          # TODO(aramk) This block causes a strange issue where ALL.classes is defined with
-          # defaultValue already set, though it wasn't a step earlier...
-          if allClassOptions.defaultValue?
-            throw new Error('Default value specified on field ' + itemId +
-                ' and in classOptions - only use one.')
-          allClassOptions.defaultValue = defaultValue
-          delete fieldSchema.defaultValue
       catSchemaFields[itemId] = fieldSchema
     catSchema = new SimpleSchema(catSchemaFields)
     catSchemaArgs = _.extend({


Update vector.coffee

Update vector.coffee

-Add closePath fixes gap visible in Chrome pdf viewer with large line widths
-Fix for too large corner radius

--- lib/mixins/vector.coffee
+++ lib/mixins/vector.coffee
@@ -71,6 +71,8 @@
     @addContent "#{x} #{y} #{w} #{h} re"

   roundedRect: (x, y, w, h, r = 0) ->
+    r = Math.min(r, 0.5 * w, 0.5 * h)
+
     # amount to inset control points from corners (see `ellipse`)
     c = r * (1.0 - KAPPA)

@@ -83,6 +85,7 @@
     @bezierCurveTo x + c, y + h, x, y + h - c, x, y + h - r
     @lineTo x, y + r
     @bezierCurveTo x, y + c, x + c, y, x + r, y
+    @closePath()

   ellipse: (x, y, r1, r2 = r1) ->
     # based on http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas/2173084#2173084


fixes admin custom field tabs and admin delete cancel link

--- app/assets/javascripts/crm.js.coffee
+++ app/assets/javascripts/crm.js.coffee
@@ -266,8 +266,8 @@

     #----------------------------------------------------------------------------
-    flick: (el, action) ->
-      $el = $(el)
+    flick: (id, action) ->
+      $el = $("#" + id)
       if $el.length
         switch action
           when "show"
@@ -416,16 +416,16 @@
   # Admin Field Tabs
   $(document).on "click", "*[data-tab-class]", (event) ->
     event.preventDefault()
-    $el = $(event.target)
-
-    for field in $(".fields")
-      $(field).hide()
-
-    for tab in $(".inline_tabs ul li")
-      $(tab).removeClass "selected"
+    $el = $(this)
+
+    $(".fields").each ->
+      $(this).hide()
+
+    $(".inline_tabs ul li").each ->
+      $(this).removeClass "selected"

     klass = $el.data("tab-class")
-    $(klass + "_section").show()
+    $("#" + klass + "_section").show()
     $el.addClass "selected"

 ) jQuery


Set slaveOk=true so queries can hit secondaries.

--- lib/mongoose-repl.coffee
+++ lib/mongoose-repl.coffee
@@ -48,8 +48,10 @@
         res = vm.runInContext js, context, filename
       catch err then return cb format_error err

-      if res instanceof mongoose.Query then res.exec (err, doc) -> cb err, doc
-      else cb null, res
+      if res instanceof mongoose.Query
+        res.setOptions(slaveOk: true).exec (err, doc) -> cb err, doc
+      else
+        cb null, res

   conn.once 'open', ->
     console.log "Using db: #{conn.name}"


Graph width fix

Graph width fix

--- app/scripts/results.coffee
+++ app/scripts/results.coffee
@@ -1661,6 +1661,8 @@
                 padding :
                     top : 0.1
                     right : 0.01
+            width = $(".tab-pane").width()
+            graph.setSize({width: width})
             graph.render()
             window._graph = @graph = graph

@@ -1670,7 +1672,10 @@

             $(window).on "resize", _.throttle(() =>
                 if @$result.is(":visible")
+                    width = $(".tab-pane").width()
                     graph.setSize()
+                    @preview.configure({width: width})
+                    @preview.render()
                     graph.render()
             , 200)


reorganize code a bit; cleanup

reorganize code a bit; cleanup

--- lib/assets/javascripts/stark/state.js.coffee
+++ lib/assets/javascripts/stark/state.js.coffee
@@ -78,19 +78,27 @@
         @log "will ajax load:", @[key]
         needed.push @[key]

-    # old implementation: we only copied data which was explicitly requested
-    # _.eachR @, @models, (model, key) ->
-    #   if data[key]
-    #     # copy into current [state] scope
-    #     @[key] = data[key]
+    return needed

-    #   else if _.isObject(model)
-    #     # create new model to be fetched
-    #     @[key] = new model(data)
-    #     @log "will ajax load:", @[key]
-    #     needed.push @[key]
+  # Return the URL that represents this state (substituting any params in @url)
+  #
+  # While @url is used matching URLs to States, create_url() is used for updating
+  # the url in the address bar or creating a link to the state
+  create_url: ->
+    url = @url
+    for name in @route.paramNames
+      if name.match(/_id$/)
+        v = name.replace(/_id$/, '')
+        if @[v] && @[v].id
+          url = url.replace(":#{name}", @[v].id)
+        else
+          @log "WARNING: couldn't find substitution for #{name} in #{@url}"
+          return false

-    return needed
+      else if @[name] and _.isString(@[name])
+        url = url.replace(":#{name}", @[name])
+
+    return url

   # This is called by Stark when the state is loaded but just before rendering.
   # Must always return true when validate succeeds.
@@ -116,26 +124,6 @@
   deactivate: ->
     # NO-OP

-  # Return the URL that represents this state (substituting any params in @url)
-  #
-  # While @url is used matching URLs to States, create_url() is used for updating
-  # the url in the address bar or creating a link to the state
-  create_url: ->
-    url = @url
-    for name in @route.paramNames
-      if name.match(/_id$/)
-        v = name.replace(/_id$/, '')
-        if @[v] && @[v].id
-          url = url.replace(":#{name}", @[v].id)
-        else
-          @log "WARNING: couldn't find substitution for #{name} in #{@url}"
-          return false
-
-      else if @[name] and _.isString(@[name])
-        url = url.replace(":#{name}", @[name])
-
-    return url
-
   # Cleanup any resources used by the state. Should remove all views and unbind any events
   dispose: (new_state) ->
     @log "disposing of state", @name, @


added coffee-script 2

added coffee-script 2

--- tasks/test.coffee
+++ tasks/test.coffee
@@ -1,2 +1,2 @@
-#! node_modules/coffee-script/bin/coffee
+#!node_modules/coffee-script/bin/coffee
 console.log 'TESTING FOR DAYS YO'


Don't contaminate next field with current value

Fixes #3592

--- app/assets/volume/spreadsheet.coffee
+++ app/assets/volume/spreadsheet.coffee
@@ -1102,7 +1102,7 @@
         editSelect = (event) ->
           editInput.value = @text
           editScope.unedit(event)
-          @text
+          return # inputCompleter needs an undefined to do nothing

         editScope.identCompleter = (input) ->
           info = editScope.info


Tidy up testing/logging

Tidy up testing/logging

--- app/coffee/S3PersistorManager.coffee
+++ app/coffee/S3PersistorManager.coffee
@@ -49,9 +49,9 @@
 			if res.statusCode != 200
 				logger.err bucketName:bucketName, key:key, fsPath:fsPath, "non 200 response from s3 putting file"
 				return callback("non 200 response from s3 on put file")
-			#LocalFileWriter.deleteFile fsPath, (err)->
-			logger.log res:res,  bucketName:bucketName, key:key, fsPath:fsPath,"file uploaded to s3"
-			callback(err)
+			LocalFileWriter.deleteFile fsPath, (err)->
+				logger.log res:res,  bucketName:bucketName, key:key, fsPath:fsPath,"file uploaded to s3"
+				callback(err)
 		putEventEmiter.on "error", (err)->
 			logger.err err:err,  bucketName:bucketName, key:key, fsPath:fsPath, "error emmited on put of file"
 			callback err
@@ -101,9 +101,10 @@
 			callback(err)

 	deleteDirectory: (bucketName, key, _callback)->
+		# deleteMultiple can call the callback multiple times so protect against this.
 		callback = (args...) ->
-			logger.log key: key, bucketName: bucketName, args: args, "calling delete callback"
 			_callback(args...)
+			_callback = () ->

 		logger.log key: key, bucketName: bucketName, "deleting directory"
 		s3Client = knox.createClient
@@ -111,7 +112,6 @@
 			secret: settings.filestore.s3.secret
 			bucket: bucketName
 		s3Client.list prefix:key, (err, data)->
-			logger.log data: data, key: key, bucketName: bucketName, "got file list"
 			keys = _.map data.Contents, (entry)->
 				return entry.Key
 			s3Client.deleteMultiple keys, callback


Fix typo

Fix typo

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -2434,7 +2434,7 @@
             get_result_callback = (error, data) =>
                 if @id_execution is id_execution
                     if error?
-                        if @queries.lenth > 1
+                        if @queries.length > 1
                             @results_view.render_error(@query, error)
                         else
                             @results_view.render_error(null, error)


More capitalization...

More capitalization...

--- test/tic_tac_toe/service/handlerTest.coffee
+++ test/tic_tac_toe/service/handlerTest.coffee
@@ -1,6 +1,6 @@
 Handler = require '../../../lib/tic_tac_toe/service/handler'
 types = require '../../../lib/tic_tac_toe/service/types/tic_tac_toe_types'
-TicTacToe = require '../../../lib/tic_tac_toe/service/types/tictactoe'
+TicTacToe = require '../../../lib/tic_tac_toe/service/types/TicTacToe'
 TicTacToeFactory = require '../../../lib/tic_tac_toe/factory'
 Factory = require '../../factory'
 should = require("should")


close a few more todos

close a few more todos

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -1,18 +1,8 @@
+# TODO ATN
 # views should not completely redraw every added row
-# when feed: replace query time in statusbar with special changefeed ticker
-# stop ticking when changefeed gets aborted
 # many of the fields of the parent view don't get updated when they change
-# some code assumes that result is an array
 # table view shows "no results" when it means "no more results" or "no results yet"
-# abort button blinks when runnign a normal query
-# abort blinks constantly when running a changefeed
 # "Possibly unhandled RqlRuntimeError. This HTTP connection is not open" when aborting a query after a changefeed query
-
-# ticker shows throughout
-# move more results button into individual views
-# returned/displayed/skipped has extra comma and is generally ugly
-
-# round-trip time for cursors is inconsistent

 # Copyright 2010-2012 RethinkDB, all rights reserved.
 module 'DataExplorerView', ->
@@ -2506,6 +2496,8 @@
                     }

         abort_query: =>
+            @disable_toggle_executing = false
+            @toggle_executing false
             @state.query_result?.force_end_gracefully()
             @driver_handler.close_connection()

@@ -2554,11 +2546,13 @@
         toggle_executing: (executing) =>
             if executing == @executing
                 return
-            else if executing is true
+            if @disable_toggle_executing
+                return
+            if executing is true
                 @executing = true
                 @timeout_show_abort = setTimeout =>
-                    #TODO Delay a few ms
-                    @$('.loading_query_img').show()
+                    if not @state.query_result?.is_feed
+                        @$('.loading_query_img').show()
                     @$('.execute_query').hide()
                     @$('.abort_query').show()
                 , @delay_show_abort
@@ -2603,12 +2597,21 @@
                             events:
                                 error: (query_result, err) =>
                                     @results_view_wrapper.render_error(@query, err)
+                                ready: (query_result) =>
+                                    if query_result.is_feed
+                                        @toggle_executing true
+                                        @disable_toggle_executing = true
+                                        for event in ['end', 'discard', 'error']
+                                            query_result.on event, () =>
+                                                @disable_toggle_executing = false
+                                                @toggle_executing false

                         @state.query_result = query_result

                         @results_view_wrapper.set_query_result
                             query_result: @state.query_result

+                    @disable_toggle_executing = false
                     @driver_handler.run_with_new_connection rdb_query,
                         optargs:
                             binaryFormat: "raw"
@@ -2929,7 +2932,6 @@
                     if @query_result.is_feed
                         latest = @query_result.results[-@parent.container.limit .. ]
                         latest.reverse()
-                        console.log latest
                         return latest
                     else
                         return @query_result.results[@query_result.position .. @query_result.position + @parent.container.limit]
@@ -2937,7 +2939,7 @@
         fetch_batch_rows:  =>
             if @query_result.type is not 'cursor'
                 return
-            if @query_result.size() < @query_result.position + @parent.container.limit
+            if @query_result.is_feed or @query_result.size() < @query_result.position + @parent.container.limit
                 @query_result.once 'add', (query_result, row) =>
                     @add_row row
                     @fetch_batch_rows()
@@ -3746,7 +3748,7 @@
         constructor: (options) ->
             @container = options.container
             @concurrent = 0
-            @total_duration = 0
+            @total_duration = 0

         _begin: =>
             if @concurrent == 0


finish unit test for incremental update

finish unit test for incremental update

--- test/unit/coffee/ResourceWriterTests.coffee
+++ test/unit/coffee/ResourceWriterTests.coffee
@@ -54,7 +54,6 @@
 					.should.equal true

 		it "should store the sync state", ->
-			console.log "CHECKING", @syncState, @basePath
 			@ResourceWriter.storeSyncState
 				.calledWith(@syncState, @basePath)
 				.should.equal true
@@ -77,7 +76,7 @@
 			@ResourceWriter.checkSyncState = sinon.stub().callsArgWith(2, null, true)
 			@ResourceWriter.storeSyncState = sinon.stub().callsArg(2)
 			@ResourceListManager.saveResourceList = sinon.stub().callsArg(2)
-			@ResourceListManager.loadResourceList = sinon.stub().callsArg(1)
+			@ResourceListManager.loadResourceList = sinon.stub().callsArgWith(1, null, @resources)
 			@ResourceWriter.syncResourcesToDisk({
 				project_id: @project_id,
 				syncType: "incremental",


Handle group/ungroup

Handle group/ungroup

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -316,6 +316,7 @@
             value: ['number', 'bool', 'string', 'array', 'object', 'time']
             any: ['number', 'bool', 'string', 'array', 'object', 'stream', 'selection', 'table', 'db', 'r', 'error' ]
             sequence: ['table', 'selection', 'stream', 'array']
+            grouped_stream: ['stream', 'array']

         # Convert meta types (value, any or sequence) to an array of types or return an array composed of just the type
         convert_type: (type) =>
@@ -910,11 +911,12 @@

                     if result.suggestions?.length > 0
                         for suggestion, i in result.suggestions
-                            result.suggestions.sort() # We could eventually sort things earlier with a merge sort but for now that should be enough
-                            @current_suggestions.push suggestion
-                            @.$('.suggestion_name_list').append @template_suggestion_name
-                                id: i
-                                suggestion: suggestion
+                            if suggestion isnt 'ungroup(' or @grouped_data is true
+                                result.suggestions.sort() # We could eventually sort things earlier with a merge sort but for now that should be enough
+                                @current_suggestions.push suggestion
+                                @.$('.suggestion_name_list').append @template_suggestion_name
+                                    id: i
+                                    suggestion: suggestion
                     else if result.description?
                         @description = result.description

@@ -1258,12 +1260,15 @@
             @grouped_data = @count_group_level(stack).count_group > 0

             if result.suggestions?.length > 0
+                show_suggestion = false
                 for suggestion, i in result.suggestions
-                    @current_suggestions.push suggestion
-                    @.$('.suggestion_name_list').append @template_suggestion_name
-                        id: i
-                        suggestion: suggestion
-                if @state.options.suggestions is true
+                    if suggestion isnt 'ungroup(' or @grouped_data is true
+                        show_suggestion = true
+                        @current_suggestions.push suggestion
+                        @.$('.suggestion_name_list').append @template_suggestion_name
+                            id: i
+                            suggestion: suggestion
+                if @state.options.suggestions is true and show_suggestion is true
                     @show_suggestion()
                 else
                     @hide_suggestion()


Track  which is slightly less efficient than directly computing the position of the cursor, but it's more robust

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -195,19 +195,10 @@
             else
                 @num_char_per_line = @default_num_char_per_line

-        # Compute the number of extra lines because of long lines
-        compute_extra_lines: =>
-            query_lines = @codemirror.getValue().split '\n'
-            i = 0
-            extra_lines = 0
-            while i < query_lines.length-1
-                extra_lines += Math.floor(query_lines[i].length/@num_char_per_line)
-                i++
-            extra_lines += Math.floor(@codemirror.getCursor().ch/@num_char_per_line)
-            return extra_lines
-
         move_suggestion: =>
-            margin_left = (@codemirror.getCursor().ch%@num_char_per_line)*8+27
+
+            margin_left = parseInt(@.$('.CodeMirror-cursor').css('left').replace('px', ''))+27
+
             @.$('.arrow').css 'margin-left', margin_left

             if margin_left < 200
@@ -223,7 +214,7 @@

         #TODO refactor show_suggestion, show_suggestion_description, add_description
         show_suggestion: =>
-            margin = ((@codemirror.getCursor().line+1+@compute_extra_lines())*@line_height) + 'px'
+            margin = (parseInt(@.$('.CodeMirror-cursor').css('top').replace('px', ''))+@line_height)+'px'
             @.$('.suggestion_full_container').css 'margin-top', margin
             @.$('.arrow').css 'margin-top', margin
             @.$('.suggestion_name_list').css 'display', 'block'
@@ -231,7 +222,7 @@
             @show_or_hide_arrow()

         show_suggestion_description: =>
-            margin = ((@codemirror.getCursor().line+1+@compute_extra_lines())*@line_height) + 'px'
+            margin = (parseInt(@.$('.CodeMirror-cursor').css('top').replace('px', ''))+@line_height)+'px'
             @.$('.suggestion_full_container').css 'margin-top', margin
             @.$('.arrow').css 'margin-top', margin
             @.$('.suggestion_description').css 'display', 'block'
@@ -328,7 +319,8 @@

         add_description: (fn) =>
             if @descriptions[fn]?
-                margin = ((@codemirror.getCursor().line+1+@compute_extra_lines())*@line_height) + 'px'
+                margin = (parseInt(@.$('.CodeMirror-cursor').css('top').replace('px', ''))+@line_height)+'px'
+
                 @.$('.suggestion_full_container').css 'margin-top', margin
                 @.$('.arrow').css 'margin-top', margin


Silence the pubnub "Missing Message" errors.

--- src/application.coffee
+++ src/application.coffee
@@ -32,6 +32,8 @@

 		# Redefine original function
 		publish = (message) ->
+			# Stop pubnub logging loads of "Missing Message" errors, as they are quite distracting
+			message or= ' '
 			pubnub.publish({ channel, message })

 		# Replay queue now that we have initialised the publish function


#24 cleanup

#24 cleanup

--- lib/provider/fold.coffee
+++ lib/provider/fold.coffee
@@ -1,6 +1,7 @@
 _ = require 'underscore-plus'
 Base = require './base'
 settings = require '../settings'
+{Point} = require 'atom'

 getCodeFoldStartRowsAtIndentLevel = (editor, indentLevel) ->
   rows = [0..editor.getLastBufferRow()]
@@ -30,22 +31,18 @@
     @ui.refresh()

   updateFoldLevel: (relativeLevel) ->
-    newFoldLevel = @foldLevel + relativeLevel
-    @foldLevel = Math.max(0, newFoldLevel)
+    @foldLevel = Math.max(0, @foldLevel + relativeLevel)
     @refresh()

   getItems: ->
     if @items?
       @items
     else
-      @items = []
       startRows = getCodeFoldStartRowsAtIndentLevel(@editor, @foldLevel)
+      filePath = @editor.getPath()
       rows = _.sortBy(_.uniq(startRows), (row) -> row)
-      filePath = @editor.getPath()
-      for row, i in rows
-        item = {filePath, point: [row, 0], text: @editor.lineTextForBufferRow(row)}
-        @items.push(item)
-      @items
+      @items = rows.map (row) =>
+        {filePath, point: new Point(row, 0), text: @editor.lineTextForBufferRow(row)}

   viewForItem: ({text}) ->
     text


Add docs for confirm

Add docs for confirm

--- lib/rails/confirm.coffee
+++ lib/rails/confirm.coffee
@@ -1,7 +1,15 @@
+# Prompt native confirm dialog for `data-confirm` links.
+#
+#     Delete
+
 $(document).on 'click', 'a[data-confirm]', (event) ->
   if message = $(this).attr 'data-confirm'
+    # Prompt message with native confirm dialog
     unless confirm message
+      # Prevent other handlers on the document from running
       event.stopImmediatePropagation()
+      # Prevent default action from running
       return false

+  # Return `undefined` so we don't stop the event propagation.
   return


#1163 and #1164 - manual editing of unsupported coumns

--- jsapp/xlform_model_view/view.rowDetail.coffee
+++ jsapp/xlform_model_view/view.rowDetail.coffee
@@ -27,10 +27,7 @@
       unless @model.key
         throw new Error "RowDetail does not have key"
       @extraClass = "xlf-dv-#{@model.key}"
-      if (viewMixin = viewRowDetail.DetailViewMixins[@model.key])
-        _.extend(@, viewMixin)
-      else
-        console?.error "Couldn't build view for column: ", @model.key
+      _.extend(@, viewRowDetail.DetailViewMixins[@model.key] || viewRowDetail.DetailViewMixins.default)
       @$el.addClass(@extraClass)

     render: ()->


#497 - Form titles with 255+ characters cannot be saved

--- jsapp/xlform_model_view/view.surveyApp.coffee
+++ jsapp/xlform_model_view/view.surveyApp.coffee
@@ -92,7 +92,10 @@
         @emptySurveyXlfRowSelector.expand()

       if @features.displayTitle
-        $viewUtils.makeEditable @, @survey.settings, '.form-title', property:'form_title'
+        $viewUtils.makeEditable @, @survey.settings, '.form-title', property:'form_title', validate: () ->
+          if value.length > 255
+            return "Length cannot exceed 255 characters, is " + value.length + " characters."
+          return
       else
         @$(".survey-header__inner").hide()


remove redundant domain mapping

remove redundant domain mapping

--- lib/runnable/sync.coffee
+++ lib/runnable/sync.coffee
@@ -112,8 +112,6 @@
     #Assume current author by default
     remote = "#{user_name}/#{remote}"  if remote.indexOf('/') is -1

-    options.domain = "qa.forio.com" if options.domain is "qa"
-
     config =
         local: local
         remote: remote


(naming)

(naming)

--- test/coffeescripts/Settings_spec.js.coffee
+++ test/coffeescripts/Settings_spec.js.coffee
@@ -12,7 +12,7 @@
     expect(empty_settings.colors).to.exist

   describe "flags", ->
-    it "reads a move string", ->
+    it "recognizes flags", ->
       settings = new Settings({ data: (name) -> {flags: "fast shiny"}[name] })
       expect(settings.flags).to.equal("fast shiny")
       expect(settings.flag("fast")).to.be.true


Fixed discard in set class

Fixed discard in set class

--- src/builtin/set.coffee
+++ src/builtin/set.coffee
@@ -165,7 +165,8 @@
     return

   discard: (elem) ->
-    @value.pop(elem)
+    if @value.__contains__(elem)
+      @value.pop(elem)
     return

   intersection: (others...) ->


Made sure sprites are idle and properly positioned when they load.

--- app/lib/surface/CocoSprite.coffee
+++ app/lib/surface/CocoSprite.coffee
@@ -100,6 +100,7 @@
         @actions = @thangType.getActions()
         @buildFromSpriteSheet result
         @createMarks()
+        @queueAction 'idle'

   finishSetup: ->
     @updateBaseScale()


Trying out text shadow coloring based on team for text event labels.

--- app/lib/surface/CocoSprite.coffee
+++ app/lib/surface/CocoSprite.coffee
@@ -260,7 +260,8 @@
       @handledDisplayEvents[event] = true
       options = JSON.parse(event[5...])
       label = new createjs.Text options.text, "bold #{options.size or 16}px Arial", options.color or '#FFF'
-      label.shadow = new createjs.Shadow '#000', 0, 0, 2
+      shadowColor = {humans: '#F00', ogres: '#00F', neutral: '#0F0', common: '#0F0'}[@thang.team] ? '#000'
+      label.shadow = new createjs.Shadow shadowColor, 1, 1, 3
       offset = @getOffset 'aboveHead'
       [label.x, label.y] = [@imageObject.x + offset.x - label.getMeasuredWidth() / 2, @imageObject.y + offset.y]
       @options.floatingLayer.addChild label


Added some extra resource tests

Added some extra resource tests

--- test/unit/services/resourceServiceSpec.coffee
+++ test/unit/services/resourceServiceSpec.coffee
@@ -84,6 +84,14 @@

         httpBackend.whenGET('http://localhost:8000/api/v1/projects/1/tags').respond(200)
         httpBackend.whenGET('http://localhost:8000/api/v1/projects/100/tags').respond(400)
+        httpBackend.whenGET('http://localhost:8000/api/v1/projects/1/issue_filters_data').respond(200)
+        httpBackend.whenGET('http://localhost:8000/api/v1/projects/100/issue_filters_data').respond(400)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/memberships?', {"test": "test"}).respond(200)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/memberships?', {"test": "bad"}).respond(400)
+        httpBackend.whenGET('http://localhost:8000/api/v1/roles?project=1').respond(200)
+        httpBackend.whenGET('http://localhost:8000/api/v1/roles?project=100').respond(400)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/roles?', {"test": "test", "project": 1}).respond(200)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/roles?', {"test": "bad", "project": 1}).respond(400)

     describe 'resource service', ->
         afterEach ->
@@ -284,3 +292,47 @@
             promise = resource.getProjectTags(100)
             promise.should.be.rejected
             httpBackend.flush()
+
+        it 'should allow to get a project issues filters data', inject (resource) ->
+            httpBackend.expectGET('http://localhost:8000/api/v1/projects/1/issue_filters_data')
+            promise = resource.getIssuesFiltersData(1)
+            httpBackend.flush()
+            promise.should.be.fullfilled
+
+            httpBackend.expectGET('http://localhost:8000/api/v1/projects/100/issue_filters_data')
+            promise = resource.getIssuesFiltersData(100)
+            promise.should.be.rejected
+            httpBackend.flush()
+
+        it 'should allow to create a membership', inject (resource) ->
+            httpBackend.expectPOST('http://localhost:8000/api/v1/memberships?', {"test": "test"})
+            promise = resource.createMembership({"test": "test"})
+            httpBackend.flush()
+            promise.should.be.fullfilled
+
+            httpBackend.expectPOST('http://localhost:8000/api/v1/memberships?', {"test": "bad"})
+            promise = resource.createMembership({"test": "bad"})
+            promise.should.be.rejected
+            httpBackend.flush()
+
+        it 'should allow to get a project roles', inject (resource) ->
+            httpBackend.expectGET('http://localhost:8000/api/v1/roles?project=1')
+            promise = resource.getRoles(1)
+            httpBackend.flush()
+            promise.should.be.fullfilled
+
+            httpBackend.expectGET('http://localhost:8000/api/v1/roles?project=100')
+            promise = resource.getRoles(100)
+            promise.should.be.rejected
+            httpBackend.flush()
+
+        it 'should allow to create a role', inject (resource) ->
+            httpBackend.expectPOST('http://localhost:8000/api/v1/roles?', {"test": "test", "project": 1})
+            promise = resource.createRole(1, {"test": "test"})
+            httpBackend.flush()
+            promise.should.be.fullfilled
+
+            httpBackend.expectPOST('http://localhost:8000/api/v1/roles?', {"test": "bad", "project": 1})
+            promise = resource.createRole(1, {"test": "bad"})
+            promise.should.be.rejected
+            httpBackend.flush()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera_fade.coffee
+++ src/camera_fade.coffee
@@ -43,7 +43,7 @@
   @param {Color} [color="black"] The color to fade from
   ###
   fadeIn: (options={}) ->
-    {alpha, color, duration} = Object.reverseMerge(options, defaultParams)
+    {alpha, color, duration} = Object.reverseMerge(options, )

     configureFade(duration, color, 0)


no longer get unnecessary context

no longer get unnecessary context

--- src/renderer/components/link-component.coffee
+++ src/renderer/components/link-component.coffee
@@ -35,10 +35,9 @@
     super props

   componentWillMount: ->
-    @store = @context.ctx.routeStore
-    state = @store.get()
-    @setState
-      routes: state.routes
+    if @props.uniqRoute
+      @setState
+        routes: @context.ctx.routeStore.get().routes
     @href = '#'

   navigate: (e) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/color.coffee
+++ test/color.coffee
@@ -83,3 +83,8 @@
   equals color.g(), 86
   equals color.b(), 85

+test "Color should parse arrays", ->
+  color = Color("Fuzzy Wuzzy Brown")
+  equals color.r(), 196
+  equals color.g(), 86
+  equals color.b(), 85


Update to use axios conventions

Update to use axios conventions

--- src/client/node.coffee
+++ src/client/node.coffee
@@ -25,65 +25,30 @@
       method:              blueprint.method
       headers:             blueprint.headers ? {}
       followAllRedirects:  true
-
-      # Enable proxy for automatically generating documentation
-      # proxy:               'http://localhost:4010'
-      # tunnel:              true
-      # strictSSL:           false
-
-    # Get body from previous step
-    if data.body?
-      opts.body = data.body
-
-    # Not JSON if blueprint.file or blueprint.stream is set
-    if blueprint.stream? or blueprint.file?
-      delete opts.json
-    else
-      if (['POST', 'PATCH', 'PUT'].indexOf opts.method) != -1
-        opts.json = data
-      else
-        opts.json = true
+      validateStatus:      (status) -> status < 308
+      data:                data

     @log 'request', opts, 'key', key

     new Promise (resolve, reject) =>
+      axios opts
+        .then (res) =>
+          @log 'response', res.status

-      # Read file is requested
-      if blueprint.file? and (not data.body?)
-        fs.readFile (blueprint.file data), (err, body) =>
-          return reject err if err?
-
-          data.body = body
-
-          (@request blueprint, data, key)
-            .then resolve
-            .catch reject
-        return
-
-      req = axios opts, (err, res) =>
-        if res?
-          @log 'response', res.statusCode, res.body
-
-          res.status = res.statusCode
-          res.data   = res.body
-
-        if err? or (res.status > 308) or res.data?.error?
-          _err = newError opts, res, err
+          resolve
+            url:          opts.url
+            req:          opts
+            res:          res
+            data:         res.data
+            responseText: res.data
+            status:       res.status
+            statusText:   res.statusText
+            headers:      res.headers
+        .catch (err) =>
+          _err = newError opts, err.response, err
           @log 'error', _err.type, _err.status, _err.message
           @log err.stack if err?
           return reject _err

-        resolve
-          url:          opts.url
-          req:          opts
-          res:          res
-          data:         res.data
-          responseText: res.data
-          status:       res.status
-          statusText:   res.statusText
-          headers:      res.headers
-
-      if blueprint.stream?
-        (blueprint.stream.call @, data).pipe req

 export default NodeClient


Don't append params in getDocumentRoot

Don't append params in getDocumentRoot

They were stripped by augmentedUrls match anyway

--- app/assets/javascripts/lib/utils/page_state.coffee
+++ app/assets/javascripts/lib/utils/page_state.coffee
@@ -2,7 +2,7 @@

   class PageState

-    augmentedUrls: /(.*)(?=\/rated$|\/apartments$|\/hostels-and-budget-hotels$|\/guesthouses$|\?)/
+    augmentedUrls: /(.*)(?=\/rated$|\/apartments$|\/hostels-and-budget-hotels$|\/guesthouses$)/
     checkFilters: /filters/
     checkSearch: /search/

@@ -25,7 +25,7 @@
       window.location.hash = hash

     getDocumentRoot: ->
-      url = @getSlug() + @getParams()
+      url = @getSlug()
       url = if @augmentedUrls.test(url) then url.match(@augmentedUrls)[1] else url

     hasFiltered: ->


テーブル名をクリックできるように修正

テーブル名をクリックできるように修正

--- app/assets/javascripts/ponytail/views/table.coffee
+++ app/assets/javascripts/ponytail/views/table.coffee
@@ -2,6 +2,7 @@
   className: "table"
   events:
     "click .edit_table": "clickEditTable"
+    "click .table_name span": "clickEditTable"
     "keyup .table_name input": "keyupTableNameInput"
     "blur  .table_name input": "blurTableName"
     "click .drop_table": "clickDropTable"


Set logo to display at a fixed width

Set logo to display at a fixed width

Add a width attribute to the logo style for syndicated tools

--- app/assets/javascripts/syndication/tools.js.coffee
+++ app/assets/javascripts/syndication/tools.js.coffee
@@ -9,7 +9,7 @@
   linkHref: "https://www.moneyadviceservice.org.uk/"
   linkTarget: "_blank"
   logoSrc: "https://www.moneyadviceservice.org.uk/assets/logo_en.png"
-  logoStyles: "display: block; margin-bottom: 8px;"
+  logoStyles: "display: block; margin-bottom: 8px; width: 300px"
   logoAltText: "Money Advice Service"

   toolConfig:


Fixes #641, the list of levels in editor reloads properly upon changing language

--- app/views/kinds/SearchView.coffee
+++ app/views/kinds/SearchView.coffee
@@ -53,6 +53,7 @@
     hash = document.location.hash[1..]
     searchInput = @$el.find('#search')
     searchInput.val(hash) if hash?
+    delete @collection?.term
     searchInput.trigger('change')
     searchInput.focus()


chore(merge): commits from pull

chore(merge): commits from pull

--- src/client/scripts/constants/components/api/modal-api.coffee
+++ src/client/scripts/constants/components/api/modal-api.coffee
@@ -10,7 +10,7 @@
 ,
 	attribute: 'header'
 	description: '
-		Coming soon...
+		Add content in the header using slot attribute equals header.
 	'
 	options: null
 	type: 'html | string'
@@ -18,7 +18,7 @@
 ,
 	attribute: 'footer'
 	description: '
-		Coming soon...
+		Add content in the footer using slot attribute equals footer.
 	'
 	options: null
 	type: 'html | string'


Fix reply/cancel toggle after posting a reply

--- assets/javascripts/views/reply_post_form.js.coffee
+++ assets/javascripts/views/reply_post_form.js.coffee
@@ -24,7 +24,6 @@
     $form = @$form
     html = @html
     @on 'ready', => $form.html(html)
-    @on 'ready', => $form.parent().hide()

   getReplyButton: =>
     key = if @is_repost then 'reply_repost' else 'reply'


fixed tests after module.export refactor

--- bokehjs/test/test_renderer/test_tile/test_tile_renderer.coffee
+++ bokehjs/test/test_renderer/test_tile/test_tile_renderer.coffee
@@ -130,7 +130,7 @@

   describe "tms tile source", ->
     url = 'http://c.tiles.mapbox.com/v3/examples.map-szwdot65/{Z}/{X}/{Y}.png'
-    source = new TMSTileSource(url)
+    source = new TMSTileSource.Model(url)

     it "should get tiles for extent correctly", ->
       T.expect_mercator_tile_counts(source)
@@ -139,7 +139,7 @@
       tile_options =
         x_origin_offset : 0
         y_origin_offset : 0
-      offset_source = new TMSTileSource(tile_options)
+      offset_source = new TMSTileSource.Model(tile_options)
       expect(offset_source.get('x_origin_offset')).to.be.equal(0)
       expect(offset_source.get('y_origin_offset')).to.be.equal(0)

@@ -147,7 +147,7 @@
       tile_options =
         x_origin_offset : 0
         y_origin_offset : 0
-      offset_source = new TMSTileSource(tile_options)
+      offset_source = new TMSTileSource.Model(tile_options)
       bounds = offset_source.get_tile_meter_bounds(0, 0, 16)
       expect(bounds).to.include(0)

@@ -160,7 +160,7 @@
     tile_options =
       url : 'http://mt0.google.com/vt/lyrs=m@169000000&hl=en&x={X}&y={Y}&z={Z}&s=Ga'

-    source = new WMTSTileSource(tile_options)
+    source = new WMTSTileSource.Model(tile_options)

     it "should get tiles for extent correctly", ->
       T.expect_mercator_tile_counts(source)
@@ -184,7 +184,7 @@
   describe "quadkey tile source", ->
     tile_options =
       url : 'http://t0.tiles.virtualearth.net/tiles/a{Q}.jpeg?g=854&mkt=en-US&token=Anz84uRE1RULeLwuJ0qKu5amcu5rugRXy1vKc27wUaKVyIv1SVZrUjqaOfXJJoI0'
-    source = new QUADKEYTileSource(tile_options)
+    source = new QUADKEYTileSource.Model(tile_options)

     it "should get tiles for extent correctly", ->
       T.expect_mercator_tile_counts(source)
@@ -251,7 +251,7 @@
       tile_options =
         url : 'http://c.tile.openstreetmap.org/{Z}/{X}/{Y}.png'

-      source = new TMSTileSource(tile_options)
+      source = new TMSTileSource.Model(tile_options)

       [xmin, ymin, xmax, ymax, level] = [-90.283741, 29.890626, -89.912952,
                                           30.057766, 11]


Better code style

Better code style

--- core/app/backbone/views/evidence/ndp_evidence_vote_view.coffee
+++ core/app/backbone/views/evidence/ndp_evidence_vote_view.coffee
@@ -6,8 +6,8 @@
   template: 'evidence/ndp_evidence_vote'

   events:
-    "click .js-up": "_on_up_vote"
-    "click .js-down":  "_on_down_vote"
+    "click .js-up":   "_on_up_vote"
+    "click .js-down": "_on_down_vote"

   initialize: ->
     @bindTo @model, "change", @render, @


消除 express 的警告

消除 express 的警告

--- core/index.coffee
+++ core/index.coffee
@@ -15,7 +15,8 @@

 i18next.registerAppHelper app
 app.use i18next.handle
-app.use express.bodyParser()
+app.use express.json()
+app.use express.urlencoded()
 app.use express.cookieParser()
 app.use express.logger('dev')


tutorial list wip

tutorial list wip

--- core/utils.coffee
+++ core/utils.coffee
@@ -87,6 +87,12 @@

   stripTags:(value)->
     value.replace /<(?:.|\n)*?>/gm, ''
+
+  proxifyUrl:(url="")->
+    if url is ""
+      "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
+    else
+      "https://api.koding.com/1.0/image.php?url="+ encodeURIComponent(url)

   applyMarkdown: (text)->
     # problems with markdown so far:


Detect uncaught exceptions.

Detect uncaught exceptions.

--- lib.coffee
+++ lib.coffee
@@ -74,6 +74,19 @@
     ClassyTestCase.runOnServer @tearDownServer
     ClassyTestCase.runOnBoth @tearDown

+    # Test-local internal variables.
+    @_internal = {}
+
+    if Meteor.isClient
+      # Capture uncaught exceptions.
+      originalOnError = window.onerror
+      window.onerror = (args...) =>
+        @_onError args...
+        originalOnError.apply @, args if originalOnError
+
+      @_internal.capturedErrors = []
+      @_internal.currentErrors = 0
+
   @hasTests: ->
     ClassyTestCase._hasTests

@@ -90,9 +103,7 @@
     ClassyTestCase._hasTests = true
     ClassyTestCase._testRegistry[testCase.getTestName()] = testCase

-    # Setup test-local internal variables.
-    testCase._internal =
-      options: options
+    testCase._internal.options = options

     # Register the test case.
     keys = (keys for keys, method of testCase)
@@ -137,6 +148,11 @@

               @_internal.test.originalOnEvent event

+        # Prepare uncaught exceptions capture.
+        if Meteor.isClient
+          testCase._processTestFunction testChain, ->
+            @_internal.currentErrors = @_internal.capturedErrors.length
+
         testCase._processTestFunction testChain, testCase.setUpServer
         testCase._processTestFunction testChain, testCase.setUp
         testCase._processTestFunction testChain, testCase.setUpClient if Meteor.isClient
@@ -145,6 +161,14 @@
         testCase._processTestFunction testChain, testCase.tearDownServer
         testCase._processTestFunction testChain, testCase.tearDown
         testCase._processTestFunction testChain, ->
+          if Meteor.isClient
+            # Check if there were any uncaught exceptions while testing.
+            for {errorMessage, url, lineNumber, columnNumber, errorObject} in @_internal.capturedErrors[@_internal.currentErrors..]
+              @_internal.test.fail
+                type: 'uncaught_exception'
+                message: errorMessage
+                stack: errorObject.stack
+
           # Ensure that the test failed if it was registered as a failing test.
           if @_internal.options.mustFail
             # Restore exception and event handlers.
@@ -282,6 +306,10 @@
         else
           testChain.push boundItem

+  # Process uncaught exceptions.
+  _onError: (errorMessage, url, lineNumber, columnNumber, errorObject) ->
+    @_internal.capturedErrors.push {errorMessage, url, lineNumber, columnNumber, errorObject}
+
   @getTestName: ->
     @testName


Made stopping more robust.

Made stopping more robust.

--- lib.coffee
+++ lib.coffee
@@ -31,7 +31,7 @@
         # XXX COMPAT WITH METEOR 1.1.0
         originalStop = handle.stop
         handle.stop = ->
-          originalStop.call handle
+          originalStop.call handle if handle
           handle = null

     startAutorun()


Update test for parse_user_search

Update test for parse_user_search

--- src/smc-util/test/misc-test.coffee
+++ src/smc-util/test/misc-test.coffee
@@ -693,12 +693,15 @@
     it "reads in a comma separated list of usernames", ->
         exp = {email_queries: [], string_queries: [["j", "d"], ["h", "s", "y"]]}
         pus("J D, H S Y").should.be.eql exp
+    it "reads in a angle bracket wrapped email addresses", ->
+        exp = {email_queries: ["foo+bar@baz.com"], string_queries: []}
+        pus("<foo+bar@baz.com>").should.be.eql exp
     it "reads in email addresses", ->
         exp = {email_queries: ["foo+bar@baz.com"], string_queries: []}
         pus("foo+bar@baz.com").should.be.eql exp
     it "also handles mixed queries and spaces", ->
-        exp = {email_queries: ["foo+bar@baz.com"], string_queries: [["john", "doe"]]}
-        pus("   foo+bar@baz.com   , John   Doe  ").should.eql exp
+        exp = {email_queries: ["foo+bar@baz.com", "xyz@mail.com"], string_queries: [["john", "doe"]]}
+        pus("   foo+bar@baz.com   , John   Doe  ; <xyz@mail.com>").should.eql exp

 describe "delete_trailing_whitespace", ->
     dtw = misc.delete_trailing_whitespace


Fixed word_count bug

Fixed word_count bug

--- builtin/word_count.coffee
+++ builtin/word_count.coffee
@@ -29,7 +29,8 @@
                 word_count[word]++
             else
                 word_count[word] = 1
-
+        return null
+
     complete: ->
         output = []
         for word, count in word_count


Silence selection manager during editor initialization

Fixes a bug where EditorController#setEditor in Chrome wouldn't properly restore a serialized locationRange

--- src/trix/controllers/editor_controller.coffee
+++ src/trix/controllers/editor_controller.coffee
@@ -30,11 +30,13 @@
     @composition = @editor.composition
     @document = @composition.document

+    @selectionManager.delegate = null
     @createInputController()
     @createToolbarController()
     @createDocumentController()
+    @selectionManager.delegate = this
+
     @updateLocationRange()
-
     @delegate?.didSetEditor?(editor)
     @render()


Fix brittle test.

Fix brittle test.

--- test/index.coffee
+++ test/index.coffee
@@ -68,10 +68,12 @@
     MyDoc.attr.should.equal 'a'

   it "should be able to find a document", (done) ->
-    SimpleDoc.findOne _id: 'simple_doc', (err, doc) ->
-      throw err if err
-      expect(doc).to.not.be.null
-      done()
+    SimpleDoc.save _id: 'simple_doc', (err, doc) ->
+      throw err if err
+      SimpleDoc.findOne _id: 'simple_doc', (err, doc) ->
+        throw err if err
+        expect(doc).to.not.be.null
+        done()

   it "should not include findOne when you iterate over the document", ->
     for key, value of MyDoc


add error handler to "save note" function

--- app/assets/javascripts/app-ng.coffee
+++ app/assets/javascripts/app-ng.coffee
@@ -334,21 +334,24 @@
                 vm.note.title = text if text.trim().length > 0
             # invoke the api
             # TODO handle error
-            apiClient.notes.save {
+            apiClient.notes.save({
                     guid: vm.note.guid,
                     title: vm.note.title,
                     enml: enml,
-                }, (data) ->
-                    # update guid
-                    vm.note.guid = data.note.guid
-                    # set status back
-                    vm.saving_note = false
-                    alert('create/update note succeed: \n' + JSON.stringify(data))
-#        $.post('/api/v1/notes', {title: vm.note.title, contentXmlStr: final_note_xml})
-#            .done (data) ->
-#                alert('create note succeed: \n' + JSON.stringify(data))
-#            .fail (data) ->
-#                alert('create note failed: \n' + JSON.stringify(data))
+                }).$promise.then(
+                    (data) ->
+                        # update guid
+                        vm.note.guid = data.note.guid
+                        # set status back
+                        vm.saving_note = false
+                        alert('create/update note succeed: \n' + JSON.stringify(data))
+                    (error) ->
+                        # set status back
+                        vm.saving_note = false
+                        alert('create note failed: \n' + JSON.stringify(error))
+                        console.log(JSON.stringify(error))
+                )
+
 ]


sankey: link hover effect

sankey: link hover effect

--- app/assets/javascripts/d3/sankey.coffee
+++ app/assets/javascripts/d3/sankey.coffee
@@ -214,11 +214,14 @@
         attr("class", (link) ->
           "link #{link.left.get('id')} #{link.right.get('id')}"
         ).
+        attr("data-cid", (d) -> d.cid). # unique identifier
         style("stroke-width", (link) -> link.value()).
         style("stroke", (link, i) -> link.color()).
         style("fill", "none").
         style("opacity", 0.8).
-        attr("d", (link) => @link_line link.path_points())
+        attr("d", (link) => @link_line link.path_points()).
+        on("mouseover", @link_mouseover).
+        on("mouseout", @node_mouseout)

       # Node elements are grouped inside an svg element, so we can use relative
       # coordinates
@@ -287,11 +290,24 @@
             style("opacity", 0.2)
         )

+    # this is use ased link_mouseout, too
     node_mouseout: ->
       d3.selectAll(".link").
         transition().
-        duration(100).
+        duration(200).
         style("opacity", 0.8)
+
+    link_mouseover: ->
+      current_id = $(this).attr("data-cid")
+      d3.selectAll(".link").
+        each((d) ->
+          return if d.cid == current_id
+          d3.select(this).
+            transition().
+            duration(200).
+            style("opacity", 0.2)
+        )
+

     # this method is called every time we're updating the chart
     #


Remove old comments and add new donation name type ahead

--- app/assets/javascripts/donations.coffee
+++ app/assets/javascripts/donations.coffee
@@ -1,4 +1,3 @@
-# Once we can edit donations, this should behave like addOrderRow from orders.coffee
 addDonationRow = ->
   row = $ tmpl("donation-row-template", {})
   $("#donation-table tbody").append row
@@ -6,9 +5,26 @@

 expose "addInitialDonationRow", ->
   $ ->
-    # Once we can edit donations, this should behave like addOrderRows from orders.coffee
     addDonationRow()

 $(document).on "click", "#add-donation-row", (event) ->
   event.preventDefault()
   addDonationRow()
+
+buildDonationTypeahead = (names) ->
+  donatorsBloodhound = new Bloodhound({
+    datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
+    queryTokenizer: Bloodhound.tokenizers.whitespace,
+    local: names
+  })
+
+  donatorsBloodhound.initialize()
+
+  $('.typeahead').typeahead(null, {
+    displayKey: 'name',
+    source: donatorsBloodhound.ttAdapter()
+  })
+
+expose "makeDonationTypeahead", (names) ->
+  $ ->
+    buildDonationTypeahead(names)


Fix the broken caption select

Fix the broken caption select

--- templates/coffee/src/modules/video/video_caption.coffee
+++ templates/coffee/src/modules/video/video_caption.coffee
@@ -15,7 +15,6 @@
     @$('.subtitles').mouseenter(@onMouseEnter).mouseleave(@onMouseLeave)
       .mousemove(@onMovement).bind('mousewheel', @onMovement)
       .bind('DOMMouseScroll', @onMovement)
-    @$('.subtitles li[data-index]').click @seekPlayer

   captionURL: ->
     "/static/subs/#{@youtubeId}.srt.sjson"
@@ -39,6 +38,7 @@
         'data-start': @start[index]

     @$('.subtitles').html(container.html())
+    @$('.subtitles li[data-index]').click @seekPlayer

     # prepend and append an empty <li> for cosmatic reason
     @$('.subtitles').prepend($('<li class="spacing">').height(@topSpacingHeight()))


Add example API

Add example API

--- apps/examples/index.coffee
+++ apps/examples/index.coffee
@@ -3,7 +3,7 @@

 graphQL = require '../../lib/graphql'
 examples = require './examples.coffee'
-{ first, map, each } = require 'underscore'
+{ first, map, each, find } = require 'underscore'

 app = module.exports = express()

@@ -33,10 +33,21 @@
   #{require '../../components/block_v2/queries/block.coffee'}
 """

+app.get '/api/examples/:id', (req, res, next) ->
+  example = find(examples, (example) -> example.id is req.params.id)
+  send =
+    query: exampleChannelQuery
+    user: req.user
+    variables:
+      ids: example.channel_ids
+
+  graphQL(send).then ({ channels }) ->
+    example.channels = channels
+    res.json example

 app.get '/examples', (req, res, next) ->
   Promise.all(
-    map first(examples, 3), (example) ->
+    map first(examples, 6), (example) ->
       send =
         query: exampleChannelQuery
         user: req.user


Fixed scroll shadow not showing on page load (via turbolinks)

--- app/assets/javascripts/global.js.coffee
+++ app/assets/javascripts/global.js.coffee
@@ -1,15 +1,5 @@
-@ready = () ->
-  $('[data-object~="form-load"]').submit()
-
-$(document).ready(ready)
-$(document).on('page:load', ready)
-
-$(document).on('click', '#dataset-tabs a', (e) ->
-  e.preventDefault()
-  $(this).tab('show')
-)
-
 @setScrollShadow = (element) ->
+  return unless $(element)[0]
   scroll = $(element).scrollTop();
   if scroll + $(element).innerHeight() == $(element)[0].scrollHeight
     $(element).removeClass("shadow-inset-bottom")
@@ -21,10 +11,12 @@
   else
     $(element).addClass("shadow-inset-top")

-jQuery ->
+@ready = () ->
+  $('[data-object~="form-load"]').submit()
   $('.file-list-container').scroll( () ->
     setScrollShadow(this)
   )
-  $('.file-list-container').each( (index, element) ->
-    setScrollShadow($(element))
-  )
+  setScrollShadow($('.file-list-container'))
+
+$(document).ready(ready)
+$(document).on('page:load', ready)


fixes expand all / collapse all links on comments

--- app/assets/javascripts/crm.js.coffee
+++ app/assets/javascripts/crm.js.coffee
@@ -233,11 +233,11 @@
         emails_field = "#" + el_prefix + "_shown_emails"
         comment_new_field = "#" + el_prefix + "_comment_new"

-      for li in $(comment_new_field).siblings("li")
-        $li = $(li)
+      $(comment_new_field).siblings("li").each ->
+        $li = $(this)
         $a = $li.find("tt a.toggle")
         $dt = $li.find("dt")
-        unless typeof (a) is "undefined"
+        if $a.length
           if state is "Expanded"
             $($dt[0]).hide()
             $($dt[1]).show()


Better error handling in pwnfx.

Better error handling in pwnfx.

--- app/assets/javascripts/pwn-fx.js.coffee
+++ app/assets/javascripts/pwn-fx.js.coffee
@@ -140,7 +140,7 @@

   # Called when an XHR request issued by PwnFx.xhr works out.
   _xhr_onload: ->
-    if @status < 200 || @status >= 300
+    if (@status < 200 || @status >= 300) && (@status != 304)
       throw new Error(
           "XHR result ignored due to HTTP #{@status}: #{@statusText}")
     @pwnfxOnData @responseText
@@ -473,4 +473,12 @@
 window.PwnFx = PwnFx

 # Wire up the entire DOM after the document is loaded.
-window.addEventListener 'load', -> PwnFx.wire(document)
+if document.readyState == 'loading'
+  loaded = false
+  document.addEventListener 'readystatechange', ->
+    return if loaded
+    if document.readyState != 'loading'
+      PwnFx.wire(document)
+      loaded = true
+else
+  PwnFx.wire document


Done should immediately trigger done

Done should immediately trigger done

--- components/inquiry_questionnaire/views/done.coffee
+++ components/inquiry_questionnaire/views/done.coffee
@@ -1,12 +1,5 @@
-StepView = require './step.coffee'
-template = -> require('../templates/placeholder.jade') arguments...
+Backbone = require 'backbone'

-module.exports = class Done extends StepView
-  template: template
-
-  __events__:
-    'click button': 'done'
-
-  done: (e) ->
-    e.preventDefault()
+module.exports = class Done extends Backbone.View
+  initialize: ({ @state }) ->
     @state.trigger 'done'


No lookahead needed here

No lookahead needed here

h/t @aroben

--- app/assets/javascripts/task_list.coffee
+++ app/assets/javascripts/task_list.coffee
@@ -127,7 +127,7 @@
   )
   (?!                     # is not a link
     \s*                   # with optional whitespace
-    (?=\(.*?\)|\[.*?\])   # because of destination or reference
+    (?:\(.*?\)|\[.*?\])   # because of destination or reference
   )
   (?=\s)                  # is followed by whitespace
 ///


Remove panel when dismissing command-mode-input view

--- lib/view-models/vim-command-mode-input-view.coffee
+++ lib/view-models/vim-command-mode-input-view.coffee
@@ -17,7 +17,7 @@
     @singleChar = opts.singleChar
     @defaultText = opts.defaultText ? ''

-    atom.workspace.addBottomPanel(item: this, priority: 100)
+    @panel = atom.workspace.addBottomPanel(item: this, priority: 100)

     @focus()
     @handleEvents()
@@ -55,4 +55,4 @@
   remove: =>
     @stopHandlingEvents()
     atom.workspace.getActivePane().activate()
-    super()
+    @panel.destroy()


fixes empty bug

fixes empty bug

--- apps/root/client/feed_view.coffee
+++ apps/root/client/feed_view.coffee
@@ -39,8 +39,9 @@
         nextPageCallback: (request) =>
           unless request.responseJSON.items.length
             @retryCount++
-            @paginator.disable() if (@retryCount > @maxRetry) and not @collection.length
-            @showEmpty()
+            if (@retryCount > @maxRetry) and (@collection.length < 1)
+              @paginator.disable()
+              @showEmpty()

   render: ->
     @$el.html feedTemplate(feed: @collection.models, user: @current_user)


updating where new course information is loaded and updated isbusy code

--- tutor/src/components/change-student-id.cjsx
+++ tutor/src/components/change-student-id.cjsx
@@ -18,7 +18,6 @@
   componentWillMount: ->
     {courseId} = @context.router.getCurrentParams()
     CourseStore.once('course.loaded', @update)
-    CourseActions.load(courseId)

   componentWillUnmount: ->
     CourseStore.off('course.loaded', @update)
@@ -29,6 +28,8 @@
     @setState({})

   goBack: ->
+    {courseId} = @context.router.getCurrentParams()
+
     historyInfo = TransitionStore.getPrevious(@context.router)
     url = historyInfo.path or 'dashboard'
     @context.router.transitionTo(url)
@@ -39,9 +40,10 @@
     StudentIdActions.validate(courseId, newStudentId)

     if StudentIdStore.getErrors(courseId).length
-      @setState({})
+      @update()
       return

+    @setState({curId: newStudentId})
     StudentIdStore.once('student-id-saved', @saved)
     StudentIdStore.once('student-id-error', @update)
     StudentIdActions.save(courseId, {
@@ -51,6 +53,9 @@

   saved: ->
+    {courseId} = @context.router.getCurrentParams()
+    CourseActions.load(courseId)
+
     @setState({success: true})
     _.delay(@goBack, 1500)

@@ -77,10 +82,10 @@
     </BS.Panel>

   render: ->
-    {courseId} = @context.router.getCurrentParams()
     if (@state?.success) then return @renderSuccess()

-    studentId = CourseStore.getStudentId(courseId)
+    {courseId} = @context.router.getCurrentParams()
+    studentId = @state?.curId or CourseStore.getStudentId(courseId)

     <BS.Panel bsStyle='primary' className="change-id-panel">
       <BS.Row>
@@ -91,7 +96,7 @@
           onSubmit={@onSubmit}
           saveButtonLabel="Save"

-          isBusy={StudentIdStore.isSaving()}
+          isBusy={StudentIdStore.isSaving(courseId)}
           studentId={studentId}
         >
           {@renderErrors()}


fix problem if you navigate from proposal page to user page and back again

Former-commit-id: 51c4b682664d31eea8a6bef2e1b913088f77575f [formerly 256b36d71189665fdb4525cf5700da6a82fc16d7] [formerly 5d5d3a330fd411e5fd0c66753f47b10f14bc2933 [formerly 5d5d3a330fd411e5fd0c66753f47b10f14bc2933 [formerly c288ac063452667907e8fbaedb3ddd12cdd9909f]]]
Former-commit-id: b54412f93fbe886c9dd72187714b63f2d722d51b [formerly 7879577c5e806ccd13ddf836a5647bacb25893a0]
Former-commit-id: eee4979cfdea4c95c1b02e845886ed3fe7b24127
Former-commit-id: e2cf9f1720cc37f3a4a79114a943334e6b57aa80

--- app/assets/javascripts/backbone/apps/franklin/franklin_app.coffee
+++ app/assets/javascripts/backbone/apps/franklin/franklin_app.coffee
@@ -55,7 +55,7 @@
           @franklin_controller.close()
           @franklin_controller = null

-        if @franklin_controller && @franklin_controller == proposal_controller || use_existing_proposal_controller
+        if @franklin_controller && (@franklin_controller == proposal_controller && region.controlled_by == @franklin_controller) || use_existing_proposal_controller
           proposal_controller.plant region if from_root
           proposal_controller.changeState new_state
         else


Better after error on inline crud actions

--- app/assets/javascripts/effective_datatables/inline_crud.js.coffee
+++ app/assets/javascripts/effective_datatables/inline_crud.js.coffee
@@ -39,8 +39,10 @@
 # There was an error completing something
 $(document).on 'ajax:error', '.dataTables_wrapper', (event) ->
   $action = $(event.target)
-  $table = $action.closest('table')
-  $table.DataTable().flash('unable to ' + ($action.attr('title') || 'complete action'), 'danger').draw()
+
+  return true if ('' + $action.data('inline')) == 'false'
+
+  afterError($action)

   EffectiveForm.remote_form_payload = ''
   EffectiveForm.remote_form_flash = ''
@@ -146,6 +148,22 @@
     $table.DataTable().draw()
     refreshDatatables($table)

+afterError = ($action) ->
+  $table = $action.closest('table')
+  $td = $action.closest('td')
+
+  # Show dropdown
+  $td.children('.btn-group').show()
+
+  # Hide spinner
+  $td.children('svg').hide()
+
+  # Cancel
+  cancel($table)
+
+  # Don't redraw
+  $table.DataTable().flash('unable to ' + ($action.attr('title') || 'complete action'), 'danger')
+
 buildRow = (length, payload) ->
   "
#{payload}
	" +
   "" +


Don't use deprecated APIs

Don't use deprecated APIs

--- spec/go-to-line-spec.coffee
+++ spec/go-to-line-spec.coffee
@@ -7,12 +7,16 @@
   beforeEach ->
     atom.workspaceView = new WorkspaceView
     atom.workspace = atom.workspaceView.model
-    atom.workspaceView.openSync('sample.js')
-    atom.workspaceView.enableKeymap()
-    editorView = atom.workspaceView.getActiveView()
-    {editor} = editorView
-    goToLine = GoToLineView.activate()
-    editor.setCursorBufferPosition([1,0])
+
+    waitsForPromise ->
+      atom.workspace.open('sample.js')
+
+    runs ->
+      atom.workspaceView.enableKeymap()
+      editorView = atom.workspaceView.getActiveView()
+      {editor} = editorView
+      goToLine = GoToLineView.activate()
+      editor.setCursorBufferPosition([1,0])

   describe "when go-to-line:toggle is triggered", ->
     it "attaches to the root view", ->