testUtil: indented methods in util.test namespace


Removed $state.go home" on login

Removed $state.go home" on login

The login should not always go home after a successful login. Maybe it
should be redirected to the authorize state instead. To do this, I'll
need to figure out response interceptors...

--- src/app/common/services/LoginService.coffee
+++ src/app/common/services/LoginService.coffee
@@ -5,7 +5,6 @@
 		rest = Restangular.all "login"
 		okLogin = (res) ->
 			self.user["username"] = json.username
-			$state.go "home"
 		koLogin = (res) ->
 			toast = $mdToast.simple()
 				.content "Failed to log in. Reason: #{res.data.reason}"
@@ -16,6 +15,11 @@
 		self.user = {} #TODO Stubbed. Have to tell the serer to invalidate session.
 	self

-LoginService.$inject = ["$state", "$mdToast", "Restangular", "SettingsService"];
+LoginService.$inject = [
+	"$state"
+	"$mdToast"
+	"Restangular"
+	"SettingsService"
+]

-module.exports = LoginService;
+module.exports = LoginService


refs #7381

refs #7381

--- backend/app/assets/javascripts/spree/backend/line_items.js.coffee
+++ backend/app/assets/javascripts/spree/backend/line_items.js.coffee
@@ -55,8 +55,8 @@
   $.ajax(
     type: "DELETE"
     url: Spree.url(url)
-    data:
-      token: Spree.api_key
+    headers:
+      'X-Spree-Token': Spree.api_key
   ).done (msg) ->
     $('#line-item-' + line_item_id).remove()
     if $('.line-items tr.line-item').length == 0


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -588,7 +588,7 @@
       #bbox.width and bbox.height will
       #describe the size of the label text
       #labelRadius = radius - 20
-      labelRadius = radiuso + 20
+      labelRadius = radiuso * 1.2
       d.x = Math.sin(a) * labelRadius
       d.l = d.x - bbox.width / 2 - 2
       d.r = d.x + bbox.width / 2 + 2


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -50,12 +50,12 @@
             .attr("id","dropshadow")
         filter.append("feGaussianBlur")
             .attr("in","SourceAlpha")
-            .attr("stdDeviation","1.1")
+            .attr("stdDeviation",dropshadowblur)
             .attr("result","blur")
         filter.append("feOffset")
             .attr("in","blur")
-            .attr("dx",2)
-            .attr("dy",2)
+            .attr("dx",dropshadowx)
+            .attr("dy",dropshadowy)
             .attr("result","offsetBlur")
         feMerge = filter.append("feMerge")
         feMerge.append("feMergeNode")


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -433,6 +433,15 @@
             #.data(data)
             .data(pie(data))

+
+        arcs = svg.selectAll("g.slice")
+            .data(pie)
+            .enter().append("svg:g").attr("class", "slice")
+
+        arcs.append("svg:path").attr("fill", (d, i) -> color i)
+            .attr("fill-opacity", 0.4).attr("d", arc)
+
+
         path.transition()
             .duration(1500)
             #.attrTween("d", pieTween);


JVM: Move addVm to class level since we need to use it from createFreeVM method of JVM

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -266,7 +266,7 @@
     JGroup = require './group'
     JGroup.one {slug: groupSlug}, (err, group)=>
       return callback err  if err
-
+
       account.fetchUser (err, user)=>
         return callback err  if err
         return callback new Error "user is not defined"  unless user
@@ -528,56 +528,58 @@
               JPermissionSet.checkPermission client, "delete vms", group,
               (err, hasPermission)=>
                 return callback err  if err
-
+
                 @deleteVM vm, callback  if hasPermission
+
+  @addVm = ({ account, target, user, sudo, groups, groupSlug
+             type, planCode, planOwner, webHome, uid }, callback)->
+
+    return handleError new Error "user is not defined"  unless user
+    nickname = account.profile.nickname or user.username
+    uid ?= 0
+    hostnameAliases = JVM.createAliases {
+      nickname
+      type, uid, groupSlug
+    }
+
+    users = [
+      { id: user.getId(), sudo: yes, owner: yes }
+    ]
+
+    [hostnameAlias]  = hostnameAliases
+    groups          ?= []
+
+    vm = new JVM {
+      hostnameAlias
+      planOwner
+      planCode
+      webHome
+      groups
+      users
+      vmType: type
+    }
+
+    vm.save (err)->
+
+      callback? err, vm  unless err
+
+      handleError err
+      if err
+        return console.warn "Failed to create VM for ", \
+                             {users, groups, hostnameAlias}
+
+      JVM.ensureDomainSettings \
+        {account, vm, type, nickname, groupSlug}
+      # JVM.createDomains account, hostnameAliases, hostnameAlias
+      target.addVm vm, handleError
+
+  wrapGroup = (group)-> [ { id: group.getId() } ]

   do ->

     JAccount  = require './account'
     JGroup    = require './group'
     JUser     = require './user'
-
-    addVm = ({ account, target, user, sudo, groups, groupSlug
-               type, planCode, planOwner, webHome })->
-
-      return handleError new Error "user is not defined"  unless user
-      nickname = account.profile.nickname or user.username
-      uid = 0
-      hostnameAliases = JVM.createAliases {
-        nickname
-        type, uid, groupSlug
-      }
-
-      users = [
-        { id: user.getId(), sudo: yes, owner: yes }
-      ]
-
-      [hostnameAlias]  = hostnameAliases
-      groups          ?= []
-
-      vm = new JVM {
-        hostnameAlias
-        planOwner
-        planCode
-        webHome
-        groups
-        users
-        vmType: type
-      }
-
-      vm.save (err)->
-
-        handleError err
-        if err
-          return console.warn "Failed to create VM for ", \
-                               {users, groups, hostnameAlias}
-
-        JVM.ensureDomainSettings \
-          {account, vm, type, nickname, groupSlug}
-        # JVM.createDomains account, hostnameAliases, hostnameAlias
-        target.addVm vm, handleError
-
-    wrapGroup =(group)-> [ { id: group.getId() } ]

     uidFactory = null

@@ -691,7 +693,7 @@
           }).next ->

           # TODO: this special case for koding should be generalized for any group.
-          addVm {
+          JVM.addVm {
             user
             account   : member
             sudo      : yes


No point in calling `_loadTree` again in `load`

With the way the function is now, the tree that is loaded is never used.

This will be re-added in the next commit, but only for content that is
trying to be loaded by name alone.

--- src/code/providers/readonly-provider.coffee
+++ src/code/providers/readonly-provider.coffee
@@ -25,24 +25,22 @@
   @Name: 'readOnly'

   load: (metadata, callback) ->
-    @_loadTree (err, tree) ->
-      return callback err if err
-      if metadata and not isArray metadata and metadata.type is CloudMetadata.File
-        if metadata.content?
-          callback null, metadata.content
-          return
-        else if metadata.url?
-          $.ajax
-            dataType: 'json'
-            url: metadata.url
-            success: (data) ->
-              callback null, cloudContentFactory.createEnvelopedCloudContent data
-            error: -> callback "Unable to load '#{metadata.name}'"
-          return
-      if metadata?.name?
+    if metadata and not isArray metadata and metadata.type is CloudMetadata.File
+      if metadata.content?
+        callback null, metadata.content
+        return
+      else if metadata.url?
+        $.ajax
+          dataType: 'json'
+          url: metadata.url
+          success: (data) ->
+            callback null, cloudContentFactory.createEnvelopedCloudContent data
+          error: -> callback "Unable to load '#{metadata.name}'"
+        return
+      else if metadata?.name?
         callback "Unable to load '#{metadata.name}'"
-      else
-        callback "Unable to load specified content"
+    else
+      callback "Unable to load specified content"

   list: (metadata, callback) ->
     @_loadTree (err, tree) =>


fix formatting

fix formatting

--- src/google-calendar.coffee
+++ src/google-calendar.coffee
@@ -107,8 +107,8 @@
           end = moment(end).format(format)

           entry =  "[#{start}-#{end}]"
-          entry += " (#{item.location})\n" if item.location
-          entry += "#{item.summary} - #{item.htmlLink}\n"
+          entry += " (#{item.location})" if item.location
+          entry += "\n#{item.summary} - #{item.htmlLink}\n"
           entry += "hangout => #{item.hangoutLink}\n" if item.hangoutLink
           entry
         ).join("\n")


bugfix: language settings from browser

bugfix: language settings from browser

--- vendor/assets/javascripts/select.js.coffee
+++ vendor/assets/javascripts/select.js.coffee
@@ -17,7 +17,7 @@
   setLanguage: ->
     language = window.navigator.userLanguage or window.navigator.language

-    if language == "de-DE" or language == "de"
+    if language.match(/de-?.*/)
       @noResultText    = "Keine Ergebnisse für"
       @placeholderText = "Bitte auswählen"
     else


Replace student dashboard redirect with delay page

--- src/components/student-dashboard/index.cjsx
+++ src/components/student-dashboard/index.cjsx
@@ -12,12 +12,15 @@
   contextTypes:
     router: React.PropTypes.func

-  componentWillMount: ->
-    {courseId} = @context.router.getCurrentParams()
-    {is_concept_coach, webview_url} = CourseStore.get(courseId)
-
-    if is_concept_coach and webview_url
-      WindowHelpers.replaceBrowserLocation(webview_url)
+  statics:
+    # Called before the router mounts and renders the component
+    # Will display the redirect screen if course is a concept coach one
+    willTransitionTo: (transition, params, query, callback) ->
+      {courseId} = params
+      {is_concept_coach, webview_url} = CourseStore.get(courseId)
+      if is_concept_coach and webview_url
+        transition.redirect('viewStudentCCRedirect', {courseId})
+      callback()

   render: ->
     {courseId} = @context.router.getCurrentParams()


Require fix

Require fix

--- globe-geometry/mercator/mercator.coffee
+++ globe-geometry/mercator/mercator.coffee
@@ -7,6 +7,7 @@
 goog.require 'globeGeometry.LatLng'
 goog.require 'globeGeometry.LatLngBounds'
 goog.require 'globeGeometry.Point'
+goog.require 'globeGeometry.mercator.Tile'

 class globeGeometry.mercator


Prefix all pine options with '$' in preload to avoid pine warnings.

Change-Type: patch

--- lib/actions/preload.coffee
+++ lib/actions/preload.coffee
@@ -25,7 +25,7 @@
 	resin.pine.get
 		resource: 'my_application'
 		options:
-			filter:
+			$filter:
 				device_type: deviceType
 				owns__release:
 					$any:
@@ -33,9 +33,9 @@
 						$expr:
 							r:
 								status: 'success'
-			expand: preload.applicationExpandOptions
-			select: [ 'id', 'app_name', 'device_type', 'commit', 'should_track_latest_release' ]
-			orderby: 'app_name asc'
+			$expand: preload.applicationExpandOptions
+			$select: [ 'id', 'app_name', 'device_type', 'commit', 'should_track_latest_release' ]
+			$orderby: 'app_name asc'

 selectApplication = (deviceType) ->
 	visuals = require('resin-cli-visuals')


fix: Fix missing require

fix: Fix missing require

--- src/resolve-hookfiles.coffee
+++ src/resolve-hookfiles.coffee
@@ -1,4 +1,5 @@
 fs = require('fs')
+path = require('path')
 glob = require('glob')


Fix bug

Fix bug

--- LoggerFactory.coffee
+++ LoggerFactory.coffee
@@ -4,20 +4,19 @@
   @get: ->
     instance ?= new LoggerFactory

-  createLogger: (namespace, defaultLevel)->
+  createLogger: (namespace, defaultLevel = 'info')->
     log.debug 'LoggerFactory.createLogger()', arguments
-    if namespace?
-      expect(namespace).to.be.a('string').that.has.length.above(0)
-      prefix = namespace + ':'
+    expect(namespace).to.be.a('string').that.is.ok
+    prefix = namespace + ':'
     expect(Loglevel).to.be.a 'function'
     logger = Loglevel(prefix)
-    logger.setLevel(defaultLevel) if defaultLevel?
+    logger.setLevel(defaultLevel)
     return logger

-  createPackageLogger: (packageName)->
-    return @createLogger(packageName)
+  createPackageLogger: (packageName, defaultLevel)->
+    return @createLogger(packageName, defaultLevel)

-  createAppLogger: (appName)->
-    return @createLogger(appName)
+  createAppLogger: (appName, defaultLevel)->
+    return @createLogger(appName, defaultLevel)

 loglevel = LoggerFactory.get()


Trim subcomment when saving.

Trim subcomment when saving.

--- core/app/backbone/views/sub_comments/sub_comments_add_view.coffee
+++ core/app/backbone/views/sub_comments/sub_comments_add_view.coffee
@@ -30,7 +30,7 @@
     return if @submitting

     @model = new SubComment
-      content: @text()
+      content: $.trim(@text())
       created_by: currentUser

     @alertHide()


update.

update.

--- src/main/public/nz/SceneBattleResult.coffee
+++ src/main/public/nz/SceneBattleResult.coffee
@@ -85,6 +85,8 @@
       {k:'dead:'         , v:co.dead}
       {k:'attack damage:', v:co.weapon.atk.damage.total}
       {k:'shot damage:'  , v:co.ballet.atk.damage.total}
+      {k:'attack damage max:', v:co.weapon.atk.damage.max}
+      {k:'shot damage max:'  , v:co.ballet.atk.damage.max}
     ]
       cy += em
       o.addChild @textLabel(cx     ,cy,k)


Expose TerminalReporter if jasmine is already defined

This is needed if requiring jasmine above jasmine-focused but still
wanting access to the useful terminal reporter.

--- src/jasmine-focused.coffee
+++ src/jasmine-focused.coffee
@@ -1,4 +1,13 @@
-jasmine = global.jasmine ? require 'jasmine-node'
+if global.jasmine?
+  jasmine = global.jasmine
+  unless jasmine.TerminalReporter?
+    path = require 'path'
+    jasmineNodePath = require.resolve('jasmine-node')
+    reporterPath = path.join(path.dirname(jasmineNodePath), 'reporter')
+    {jasmineNode} = require(reporterPath)
+    jasmine.TerminalReporter = jasmineNode.TerminalReporter
+else
+  jasmine = require 'jasmine-node'

 setGlobalFocusPriority = (priority) ->
   env = jasmine.getEnv()


Fix spec when product type does not exist before.

--- test/integration/price-import.spec.coffee
+++ test/integration/price-import.spec.coffee
@@ -15,19 +15,27 @@
       client.products.byId(e.id).delete(e.version)
   .then (results) ->
     debug "#{_.size results} deleted."
-    Promise.resolve()
+    client.productTypes.all().fetch()
+    .then (result) ->
+      Promise.all _.map result.body.results, (e) ->
+        client.productTypes.byId(e.id).delete(e.version)
+    .then (results) ->
+      debug "#{_.size results} deleted."
+      Promise.resolve()

 getOrCreateProductType = (client) ->
-  name = 'productTypeForPriceImport'
-  client.productTypes.where("name=\"#{name}\"").fetch()
-  .then (result) ->
-    if result.total is 0
-      pt =
-        name: name
-        description: 'bla bla'
-      client.productTypes.create(pt)
-    else
-      Promise.resolve result.body.results[0]
+  new Promise (resolve, reject) ->
+    name = 'productTypeForPriceImport'
+    client.productTypes.where("name=\"#{name}\"").fetch()
+    .then (result) ->
+      if result.body.total is 0
+        pt =
+          name: name
+          description: 'bla bla'
+        client.productTypes.create(pt)
+        .then (res) -> resolve res.body
+      else
+        resolve result.body.results[0]

 createProduct = (client, productType) ->
   product =
@@ -44,7 +52,7 @@
       { sku: 'sku2', prices: [ { value: { centAmount: 777, currencyCode: 'JPY' } } ] }
       { sku: 'sku3', prices: [ { value: { centAmount: 9, currencyCode: 'GBP' } } ] }
     ]
-  client.products.create(product)
+  client.products.create product

 describe 'Price Importer integration tests', ->

@@ -68,7 +76,7 @@
       getOrCreateProductType @client
     .then (@productType) =>
       createProduct @client, @productType
-    .then (@product) =>
+    .then (@product) ->
       done()
     .catch (err) ->
       done(_.prettify err)
@@ -81,7 +89,7 @@
     .catch (err) -> done(_.prettify err)
   , 30000 # 10sec

-  it 'should update price of products', (done) ->
+  it 'should update prices of a product', (done) ->
     prices = [
       {
         sku: 'sku1'


fix https://github.com/sagemathinc/smc/issues/1776 -- "FileUseViewer::render_show_all/*_less: 2x resize_notification_list is not defined"

--- src/smc-webapp/file_use.cjsx
+++ src/smc-webapp/file_use.cjsx
@@ -520,14 +520,14 @@

     render_show_all: ->
         if @_num_missing
-            <Button key="show_all" onClick={(e)=>e.preventDefault(); @setState(show_all:true); setTimeout(resize_notification_list, 1)}>
+            <Button key="show_all" onClick={(e)=>e.preventDefault(); @setState(show_all:true)}>
                 Show {@_num_missing} more
             </Button>

     render_show_less: ->
         n = @_visible_list.length - SHORTLIST_LENGTH
         if n > 0
-            <Button key="show_less" onClick={(e)=>e.preventDefault(); @setState(show_all:false); setTimeout(resize_notification_list, 1)}>
+            <Button key="show_less" onClick={(e)=>e.preventDefault(); @setState(show_all:false)}>
                 Show {n} less
             </Button>


fix #1744 -- sagews process_sage_updates: this._syncstring.set is not a function  (syncdb fallout)

--- src/smc-webapp/sagews.coffee
+++ src/smc-webapp/sagews.coffee
@@ -850,7 +850,7 @@

         after = @editor.codemirror.getValue()
         if before != after and not @readonly
-            @_syncstring.set(after)
+            @_syncstring.set_doc(after)

     _process_sage_updates: (cm, start, stop) =>
         #dbg = (m) -> console.log("_process_sage_updates: #{m}")


webapp/sagews: when rewriting img src, always be absolute! -- issue #1417

--- src/smc-webapp/sagews.coffee
+++ src/smc-webapp/sagews.coffee
@@ -1343,7 +1343,7 @@
             if misc.startswith(src, '/')
                 file_path = ".smc/root/#{file_path}"
             {join} = require('path')
-            new_src = join(window.smc_base_url, @project_id, 'raw', file_path, src)
+            new_src = join('/', window.smc_base_url, @project_id, 'raw', file_path, src)
             y.attr('src', new_src)

     _post_save_success: () =>


use explicit returns

use explicit returns

--- js/src/tree/PermissionChangeVisitor.coffee
+++ js/src/tree/PermissionChangeVisitor.coffee
@@ -65,3 +65,5 @@
           .pluck('name')
           .unique()
           .value()
+
+        return usernames


Added getType to CompletionBackend

Added getType to CompletionBackend

--- lib/haskell-ghc-mod.coffee
+++ lib/haskell-ghc-mod.coffee
@@ -114,6 +114,10 @@

   provideCompletionBackend_0_1_0: ->
     if @process?
+      getType: (buffer, range) =>
+        new Promise (resolve) =>
+          @process.getTypeInBuffer buffer,range,(range,type) ->
+            resolve type
       listModules: (rootDir) =>
         new Promise (resolve) =>
           @process.runList rootDir, resolve


Fix cursor issues around images

Fix cursor issues around images

--- src/trix/views/image_attachment_view.coffee
+++ src/trix/views/image_attachment_view.coffee
@@ -7,7 +7,6 @@
 class Trix.ImageAttachmentView extends Trix.AttachmentView
   render: ->
     @image = document.createElement("img")
-    @image.setAttribute("contenteditable", false)
     @image.setAttribute("data-trix-identifier", @attachmentPiece.getIdentifier()) if @attachmentPiece.hasIdentifier()

     if @attachmentPiece.isPending()


Fix 63-spec

Fix 63-spec

--- spec/issues/63-spec.coffee
+++ spec/issues/63-spec.coffee
@@ -8,31 +8,29 @@

   describe "Issue 63", ->
     beforeEach ->
-      # Create a fake workspace and open a sample file
-      atom.workspaceView = new WorkspaceView
-      atom.workspaceView.openSync "sample.js"
-      atom.workspaceView.simulateDomAttachment()
+      runs ->
+        # Set to live completion
+        atom.config.set "autocomplete-plus.enableAutoActivation", true

-      # Set to live completion
-      atom.config.set "autocomplete-plus.enableAutoActivation", true
+        # Set the completion delay
+        completionDelay = 100
+        atom.config.set "autocomplete-plus.autoActivationDelay", completionDelay
+        completionDelay += 100 # Rendering delay
+        atom.workspaceView = new WorkspaceView()
+        atom.workspace = atom.workspaceView.model

-      # Set the completion delay
-      completionDelay = 100
-      atom.config.set "autocomplete-plus.autoActivationDelay", completionDelay
-      completionDelay += 100 # Rendering delay
+      waitsForPromise -> atom.workspace.open("sample.js").then (e) ->
+        editor = e
+        atom.workspaceView.simulateDomAttachment()

       # Activate the package
-      activationPromise = atom.packages.activatePackage "autocomplete-plus"
+      waitsForPromise -> atom.packages.activatePackage("autocomplete-plus").then (a) -> autocomplete = a

-      editorView = atom.workspaceView.getActiveView()
-      {editor} = editorView
-      autocomplete = new AutocompleteView editorView
+      runs ->
+        editorView = atom.workspaceView.getActiveView()
+        autocomplete = new AutocompleteView editorView

     it "it adds words to the wordlist when pressing a special character", ->
-
-      waitsForPromise ->
-        activationPromise
-
       runs ->
         editorView.attachToDom()
         editor.insertText "somethingNew"


add test spec for fix of #255

add test spec for fix of #255

This test is a bit constructed, because I do not know exact
source of core:cancel event, which triggers call of viewModel.cancel

--- spec/operators-spec.coffee
+++ spec/operators-spec.coffee
@@ -22,6 +22,19 @@
     opts.element = editor.commandModeInputView.editor.find('input').get(0)
     opts.raw = true
     keydown(key, opts)
+
+  describe "cancel operation", ->
+    it "cannot cancel operation if no operator pending", ->
+      expect(-> vimState.pushOperations(new Input(''))).toThrow()
+
+      # make sure commandModeInputView is created
+      keydown('/')
+      expect(vimState.isOperatorPending()).toBe true
+      editor.commandModeInputView.viewModel.cancel()
+
+      # now again cancel op, although there is none pending
+      expect(vimState.isOperatorPending()).toBe false
+      editor.commandModeInputView.viewModel.cancel()

   describe "the x keybinding", ->
     describe "on a line with content", ->


Begin migrating specs to new views

Begin migrating specs to new views

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -17,7 +17,7 @@
     openHandler.callCount == 1

 describe "TreeView", ->
-  [treeView, sampleJs, sampleTxt] = []
+  [treeView, root, sampleJs, sampleTxt] = []

   beforeEach ->
     tempPath = fs.realpathSync(temp.mkdirSync('atom'))
@@ -33,21 +33,21 @@
     runs ->
       atom.workspaceView.trigger 'tree-view:toggle'
       treeView = atom.workspaceView.find(".tree-view").view()
-      treeView.root = treeView.find('ol > li:first').view()
+      root = $(treeView.root)
       sampleJs = treeView.find('.file:contains(tree-view.js)')
       sampleTxt = treeView.find('.file:contains(tree-view.txt)')

-      expect(treeView.root.directory.getSubscriptionCount()).toBeGreaterThan 0
+      expect(treeView.root.directory.watchSubscription).toBeTruthy()

   afterEach ->
     temp.cleanup()

   describe ".initialize(project)", ->
     it "renders the root of the project and its contents alphabetically with subdirectories first in a collapsed state", ->
-      expect(treeView.root.find('> .header .disclosure-arrow')).not.toHaveClass('expanded')
-      expect(treeView.root.find('> .header .name')).toHaveText('tree-view')
-
-      rootEntries = treeView.root.find('.entries')
+      expect(root.find('> .header .disclosure-arrow')).not.toHaveClass('expanded')
+      expect(root.find('> .header .name')).toHaveText('tree-view')
+
+      rootEntries = root.find('.entries')
       subdir0 = rootEntries.find('> li:eq(0)')
       expect(subdir0).not.toHaveClass('expanded')
       expect(subdir0.find('.name')).toHaveText('dir1')
@@ -120,7 +120,7 @@
             atom.workspace.getActivePaneItem().saveAs(path.join(projectPath, 'test.txt'))
             expect(treeView.hasParent()).toBeTruthy()
             expect(fs.absolute(treeView.root.getPath())).toBe fs.absolute(projectPath)
-            expect(treeView.root.parent()).toMatchSelector(".tree-view")
+            expect($(treeView.root).parent()).toMatchSelector(".tree-view")

     describe "when the root view is opened to a file path", ->
       it "does not attach to the root view but does create a root node when initialized", ->
@@ -136,7 +136,7 @@
         runs ->
           treeView = atom.packages.getActivePackage("tree-view").mainModule.createView()
           expect(treeView.hasParent()).toBeFalsy()
-          expect(treeView.root).toExist()
+          expect(treeView.root).toBeTruthy()

     describe "when the root view is opened to a directory", ->
       it "attaches to the root view", ->
@@ -146,7 +146,7 @@
         runs ->
           treeView = atom.packages.getActivePackage("tree-view").mainModule.createView()
           expect(treeView.hasParent()).toBeTruthy()
-          expect(treeView.root).toExist()
+          expect(treeView.root).toBeTruthy()

     describe "when the project is a .git folder", ->
       it "does not create the tree view", ->
@@ -165,7 +165,7 @@

   describe "serialization", ->
     it "restores expanded directories and selected file when deserialized", ->
-      treeView.root.find('.directory:contains(dir1)').view().click()
+      root.find('.directory:contains(dir1)').click()

       waitsForFileToOpen ->
         sampleJs.click()
@@ -395,7 +395,7 @@

   describe "when a directory's disclosure arrow is clicked", ->
     it "expands / collapses the associated directory", ->
-      subdir = treeView.root.find('.entries > li:contains(dir1)').view()
+      subdir = root.find('.entries > li:contains(dir1)')

       expect(subdir).not.toHaveClass('expanded')

@@ -407,42 +407,42 @@
       expect(subdir).not.toHaveClass('expanded')

     it "restores the expansion state of descendant directories", ->
-      child = treeView.root.find('.entries > li:contains(dir1)').view()
+      child = root.find('.entries > li:contains(dir1)')
       child.click()

-      grandchild = child.find('.entries > li:contains(sub-dir1)').view()
+      grandchild = child.find('.entries > li:contains(sub-dir1)')
       grandchild.click()

-      treeView.root.click()
+      root.click()
       expect(treeView.root).not.toHaveClass('expanded')
-      treeView.root.click()
+      root.click()

       # previously expanded descendants remain expanded
-      expect(treeView.root.find('> .entries > li:contains(dir1) > .entries > li:contains(sub-dir1) > .entries').length).toBe 1
+      expect(root.find('> .entries > li:contains(dir1) > .entries > li:contains(sub-dir1) > .entries').length).toBe 1

       # collapsed descendants remain collapsed
-      expect(treeView.root.find('> .entries > li:contains(dir2) > .entries')).not.toHaveClass('expanded')
+      expect(root.find('> .entries > li:contains(dir2) > .entries')).not.toHaveClass('expanded')

     it "when collapsing a directory, removes change subscriptions from the collapsed directory and its descendants", ->
-      child = treeView.root.entries.find('li:contains(dir1)').view()
+      child = root.find('li:contains(dir1)')
       child.click()

-      grandchild = child.entries.find('li:contains(sub-dir1)').view()
+      grandchild = child.find('li:contains(sub-dir1)')
       grandchild.click()

-      expect(treeView.root.directory.directory.getSubscriptionCount('contents-changed')).toBe 1
-      expect(child.directory.directory.getSubscriptionCount('contents-changed')).toBe 1
-      expect(grandchild.directory.directory.getSubscriptionCount('contents-changed')).toBe 1
-
-      treeView.root.click()
-
-      expect(treeView.root.directory.directory.getSubscriptionCount('contents-changed')).toBe 0
-      expect(child.directory.directory.getSubscriptionCount('contents-changed')).toBe 0
-      expect(grandchild.directory.directory.getSubscriptionCount('contents-changed')).toBe 0
+      expect(treeView.root.directory.watchSubscription).toBeTruthy()
+      expect(child[0].directory.watchSubscription).toBeTruthy()
+      expect(grandchild[0].directory.watchSubscription).toBeTruthy()
+
+      root.click()
+
+      expect(treeView.root.directory.watchSubscription).toBeFalsy()
+      expect(child[0].directory.watchSubscription).toBeFalsy()
+      expect(grandchild[0].directory.watchSubscription).toBeFalsy()

   describe "when mouse down fires on a file or directory", ->
     it "selects the entry", ->
-      dir = treeView.root.entries.find('li:contains(dir1)').view()
+      dir = root.find('li:contains(dir1)')
       expect(dir).not.toHaveClass 'selected'
       dir.mousedown()
       expect(dir).toHaveClass 'selected'
@@ -492,7 +492,7 @@

   describe "when a directory is single-clicked", ->
     it "is selected", ->
-      subdir = treeView.root.find('.directory:first').view()
+      subdir = root.find('.directory:first')
       subdir.trigger clickEvent(originalEvent: { detail: 1 })
       expect(subdir).toHaveClass 'selected'

@@ -503,7 +503,7 @@
         sampleJs.trigger clickEvent(originalEvent: { detail: 1 })

       runs ->
-        subdir = treeView.root.find('.directory:first').view()
+        subdir = root.find('.directory:first')
         subdir.trigger clickEvent(originalEvent: { detail: 1 })
         expect(subdir).toHaveClass 'selected'
         expect(subdir).toHaveClass 'expanded'
@@ -515,42 +515,41 @@
   describe "when an directory is alt-clicked", ->
     describe "when the directory is collapsed", ->
       it "recursively expands the directory", ->
-        treeView.root.click()
+        root.click()
         treeView.root.collapse()

         expect(treeView.root).not.toHaveClass 'expanded'
-        treeView.root.trigger clickEvent({ altKey: true })
+        root.trigger clickEvent({ altKey: true })
         expect(treeView.root).toHaveClass 'expanded'

-        children = treeView.root.find('.directory')
+        children = root.find('.directory')
         expect(children.length).toBeGreaterThan 0
-        children.each (index, child) ->
-          childView = $(child).view()
-          expect(childView).toHaveClass 'expanded'
+        children.each (index, child) -> expect(child).toHaveClass 'expanded'

     describe "when the directory is expanded", ->
       parent    = null
       children  = null

       beforeEach ->
-        parent = treeView.root.find('> .entries > .directory').eq(2).view()
-        parent.expand()
+        parent = root.find('> .entries > .directory').eq(2)
+        parent[0].expand()
         children = parent.find('.expanded.directory')
         children.each (index, child) ->
-          $(child).view().expand()
+          child.expand()

       it "recursively collapses the directory", ->
-        parent.click().expand()
+        parent.click()
+        parent[0].expand()
         expect(parent).toHaveClass 'expanded'
         children.each (index, child) ->
-          $(child).view().click().expand()
-          expect($(child).view()).toHaveClass 'expanded'
+          $(child).click().expand()
+          expect($(child)).toHaveClass 'expanded'

         parent.trigger clickEvent({ altKey: true })

         expect(parent).not.toHaveClass 'expanded'
         children.each (index, child) ->
-          expect($(child).view()).not.toHaveClass 'expanded'
+          expect(child).not.toHaveClass 'expanded'
         expect(treeView.root).toHaveClass 'expanded'

   describe "when the active item changes on the active pane", ->
@@ -571,7 +570,7 @@
           atom.workspace.open('dir1/sub-dir1/sub-file1')

         runs ->
-          dirView = treeView.root.find('.directory:contains(dir1)').view()
+          dirView = root.find('.directory:contains(dir1)')
           expect(dirView).toHaveClass 'selected'

     describe "when the item has no path", ->
@@ -613,41 +612,41 @@
     describe "core:move-down", ->
       describe "when a collapsed directory is selected", ->
         it "skips to the next directory", ->
-          treeView.root.find('.directory:eq(0)').click()
+          root.find('.directory:eq(0)').click()

           treeView.trigger 'core:move-down'
-          expect(treeView.root.find('.directory:eq(1)')).toHaveClass 'selected'
+          expect(root.find('.directory:eq(1)')).toHaveClass 'selected'

       describe "when an expanded directory is selected", ->
         it "selects the first entry of the directory", ->
-          subdir = treeView.root.find('.directory:eq(1)').view()
+          subdir = root.find('.directory:eq(1)')
           subdir.click()

           treeView.trigger 'core:move-down'

-          expect(subdir.entries.find('.entry:first')).toHaveClass 'selected'
+          expect($(subdir[0].entries).find('.entry:first')).toHaveClass 'selected'

       describe "when the last entry of an expanded directory is selected", ->
         it "selects the entry after its parent directory", ->
-          subdir1 = treeView.root.find('.directory:eq(1)').view()
-          subdir1.expand()
+          subdir1 = root.find('.directory:eq(1)')
+          subdir1[0].expand()
           waitsForFileToOpen ->
-            subdir1.entries.find('.entry:last').click()
+            $(subdir1[0].entries).find('.entry:last').click()

           runs ->
             treeView.trigger 'core:move-down'
-            expect(treeView.root.find('.directory:eq(2)')).toHaveClass 'selected'
+            expect(root.find('.directory:eq(2)')).toHaveClass 'selected'

       describe "when the last directory of another last directory is selected", ->
         [nested, nested2] = []

         beforeEach ->
-          nested = treeView.root.find('.directory:eq(2)').view()
+          nested = root.find('.directory:eq(2)')
           expect(nested.find('.header').text()).toContain 'nested'
-          nested.expand()
-          nested2 = nested.entries.find('.entry:last').view()
+          nested[0].expand()
+          nested2 = $(nested[0].entries).find('.entry:last')
           nested2.click()
-          nested2.collapse()
+          nested2[0].collapse()

         describe "when the directory is collapsed", ->
           it "selects the entry after its grandparent directory", ->
@@ -656,14 +655,14 @@

         describe "when the directory is expanded", ->
           it "selects the entry after its grandparent directory", ->
-            nested2.expand()
+            nested2[0].expand()
             nested2.find('.file').remove() # kill the .gitkeep file, which has to be there but screws the test
             treeView.trigger 'core:move-down'
             expect(nested.next()).toHaveClass 'selected'

       describe "when the last entry of the last directory is selected", ->
         it "does not change the selection", ->
-          lastEntry = treeView.root.find('> .entries .entry:last')
+          lastEntry = root.find('> .entries .entry:last')
           waitsForFileToOpen ->
             lastEntry.click()

@@ -674,9 +673,9 @@
     describe "core:move-up", ->
       describe "when there is an expanded directory before the currently selected entry", ->
         it "selects the last entry in the expanded directory", ->
-          lastDir = treeView.root.find('.directory:last').view()
-          fileAfterDir = lastDir.next().view()
-          lastDir.expand()
+          lastDir = root.find('.directory:last')
+          fileAfterDir = lastDir.next()
+          lastDir[0].expand()
           waitsForFileToOpen ->
             fileAfterDir.click()

@@ -686,7 +685,7 @@

       describe "when there is an entry before the currently selected entry", ->
         it "selects the previous entry", ->
-          lastEntry = treeView.root.find('.entry:last')
+          lastEntry = root.find('.entry:last')
           waitsForFileToOpen ->
             lastEntry.click()

@@ -696,8 +695,8 @@

       describe "when there is no entry before the currently selected entry, but there is a parent directory", ->
         it "selects the parent directory", ->
-          subdir = treeView.root.find('.directory:first').view()
-          subdir.expand()
+          subdir = root.find('.directory:first')
+          subdir[0].expand()
           subdir.find('> .entries > .entry:first').click()

           treeView.trigger 'core:move-up'
@@ -706,7 +705,7 @@

       describe "when there is no parent directory or previous entry", ->
         it "does not change the selection", ->
-          treeView.root.click()
+          root.click()
           treeView.trigger 'core:move-up'
           expect(treeView.root).toHaveClass 'selected'

@@ -714,7 +713,7 @@
       it "scrolls to the top", ->
         treeView.height(100)
         treeView.attachToDom()
-        $(element).view().expand() for element in treeView.find('.directory')
+        element.expand() for element in treeView.find('.directory')
         expect(treeView.list.outerHeight()).toBeGreaterThan treeView.scroller.outerHeight()

         expect(treeView.scrollTop()).toBe 0
@@ -738,12 +737,12 @@
       it "scrolls to the bottom", ->
         treeView.height(100)
         treeView.attachToDom()
-        $(element).view().expand() for element in treeView.find('.directory')
+        element.expand() for element in treeView.find('.directory')
         expect(treeView.list.outerHeight()).toBeGreaterThan treeView.scroller.outerHeight()

         expect(treeView.scrollTop()).toBe 0
         treeView.trigger 'core:move-to-bottom'
-        expect(treeView.scrollBottom()).toBe treeView.root.outerHeight()
+        expect(treeView.scrollBottom()).toBe root.outerHeight()

         treeView.root.collapse()
         treeView.trigger 'core:move-to-bottom'
@@ -752,13 +751,13 @@
       it "selects the last entry", ->
         expect(treeView.root).toHaveClass 'selected'
         treeView.trigger 'core:move-to-bottom'
-        expect(treeView.root.find('.entry:last')).toHaveClass 'selected'
+        expect(root.find('.entry:last')).toHaveClass 'selected'

     describe "core:page-up", ->
       it "scrolls up a page", ->
         treeView.height(5)
         treeView.attachToDom()
-        $(element).view().expand() for element in treeView.find('.directory')
+        element.expand() for element in treeView.find('.directory')
         expect(treeView.list.outerHeight()).toBeGreaterThan treeView.scroller.outerHeight()

         expect(treeView.scrollTop()).toBe 0
@@ -773,7 +772,7 @@
       it "scrolls down a page", ->
         treeView.height(5)
         treeView.attachToDom()
-        $(element).view().expand() for element in treeView.find('.directory')
+        element.expand() for element in treeView.find('.directory')
         expect(treeView.list.outerHeight()).toBeGreaterThan treeView.scroller.outerHeight()

         expect(treeView.scrollTop()).toBe 0
@@ -784,7 +783,7 @@
       it "scrolls the tree view to the selected item", ->
         treeView.height(100)
         treeView.attachToDom()
-        $(element).view().expand() for element in treeView.find('.directory')
+        element.expand() for element in treeView.find('.directory')
         expect(treeView.list.outerHeight()).toBeGreaterThan treeView.scroller.outerHeight()

         treeView.moveDown()
@@ -802,9 +801,9 @@
     describe "tree-view:expand-directory", ->
       describe "when a directory entry is selected", ->
         it "expands the current directory", ->
-          subdir = treeView.root.find('.directory:first').view()
+          subdir = root.find('.directory:first')
           subdir.click()
-          subdir.collapse()
+          subdir[0].collapse()

           expect(subdir).not.toHaveClass 'expanded'
           treeView.trigger 'tree-view:expand-directory'
@@ -813,7 +812,7 @@
       describe "when a file entry is selected", ->
         it "does nothing", ->
           waitsForFileToOpen ->
-            treeView.root.find('.file').click()
+            root.find('.file').click()

           runs ->
             treeView.trigger 'tree-view:expand-directory'
@@ -821,29 +820,29 @@
     describe "tree-view:recursive-expand-directory", ->
       describe "when an collapsed root is recursively expanded", ->
         it "expands the root and all subdirectories", ->
-          treeView.root.click()
+          root.click()
           treeView.root.collapse()

           expect(treeView.root).not.toHaveClass 'expanded'
           treeView.trigger 'tree-view:recursive-expand-directory'
           expect(treeView.root).toHaveClass 'expanded'

-          children = treeView.root.find('.directory')
+          children = root.find('.directory')
           expect(children.length).toBeGreaterThan 0
           children.each (index, child) ->
-            childView = $(child).view()
-            expect(childView).toHaveClass 'expanded'
+            expect(child).toHaveClass 'expanded'

     describe "tree-view:collapse-directory", ->
       subdir = null

       beforeEach ->
-        subdir = treeView.root.find('> .entries > .directory').eq(0).view()
-        subdir.expand()
+        subdir = root.find('> .entries > .directory').eq(0)
+        subdir[0].expand()

       describe "when an expanded directory is selected", ->
         it "collapses the selected directory", ->
-          subdir.click().expand()
+          subdir.click()
+          subdir[0].expand()
           expect(subdir).toHaveClass 'expanded'

           treeView.trigger 'tree-view:collapse-directory'
@@ -853,7 +852,9 @@

       describe "when a collapsed directory is selected", ->
         it "collapses and selects the selected directory's parent directory", ->
-          subdir.find('.directory').view().click().collapse()
+          directories = subdir.find('.directory')
+          directories.click()
+          directories[0].collapse()
           treeView.trigger 'tree-view:collapse-directory'

           expect(subdir).not.toHaveClass 'expanded'
@@ -883,25 +884,27 @@
       children  = null

       beforeEach ->
-        parent = treeView.root.find('> .entries > .directory').eq(2).view()
-        parent.expand()
+        parent = root.find('> .entries > .directory').eq(2)
+        parent[0].expand()
         children = parent.find('.expanded.directory')
         children.each (index, child) ->
-          $(child).view().expand()
+          child.expand()

       describe "when an expanded directory is recursively collapsed", ->
         it "collapses the directory and all its child directories", ->
-          parent.click().expand()
+          parent.click()
+          parent[0].expand()
           expect(parent).toHaveClass 'expanded'
           children.each (index, child) ->
-            $(child).view().click().expand()
-            expect($(child).view()).toHaveClass 'expanded'
+            $(child).click()
+            child.expand()
+            expect(child).toHaveClass 'expanded'

           treeView.trigger 'tree-view:recursive-collapse-directory'

           expect(parent).not.toHaveClass 'expanded'
           children.each (index, child) ->
-            expect($(child).view()).not.toHaveClass 'expanded'
+            expect(child).not.toHaveClass 'expanded'
           expect(treeView.root).toHaveClass 'expanded'

     describe "tree-view:open-selected-entry", ->
@@ -911,10 +914,10 @@

         it "opens the file in the editor and focuses it", ->
           waitsForFileToOpen ->
-            treeView.root.find('.file:contains(tree-view.js)').click()
+            root.find('.file:contains(tree-view.js)').click()

           waitsForFileToOpen ->
-            treeView.root.trigger 'tree-view:open-selected-entry'
+            root.trigger 'tree-view:open-selected-entry'

           runs ->
             expect(atom.workspace.getActivePaneItem().getPath()).toBe atom.project.resolve('tree-view.js')
@@ -922,18 +925,19 @@

       describe "when a directory is selected", ->
         it "expands or collapses the directory", ->
-          subdir = treeView.root.find('.directory').first().view()
-          subdir.click().collapse()
+          subdir = root.find('.directory').first()
+          subdir.click()
+          subdir[0].collapse()

           expect(subdir).not.toHaveClass 'expanded'
-          treeView.root.trigger 'tree-view:open-selected-entry'
+          root.trigger 'tree-view:open-selected-entry'
           expect(subdir).toHaveClass 'expanded'
-          treeView.root.trigger 'tree-view:open-selected-entry'
+          root.trigger 'tree-view:open-selected-entry'
           expect(subdir).not.toHaveClass 'expanded'

       describe "when nothing is selected", ->
         it "does nothing", ->
-          treeView.root.trigger 'tree-view:open-selected-entry'
+          root.trigger 'tree-view:open-selected-entry'
           expect(atom.workspaceView.getActiveView()).toBeUndefined()

   describe "file modification", ->
@@ -965,14 +969,14 @@

       runs ->
         atom.workspaceView.trigger 'tree-view:toggle'
-        treeView = atom.workspaceView.find(".tree-view").view()
-        dirView = treeView.root.entries.find('.directory:contains(test-dir)').view()
+        treeView = atom.workspaceView.find(".tree-view")
+        dirView = treeView.root.entries.find('.directory:contains(test-dir)')
         dirView.expand()
-        fileView = treeView.find('.file:contains(test-file.txt)').view()
-        dirView2 = treeView.root.entries.find('.directory:contains(test-dir2)').view()
+        fileView = treeView.find('.file:contains(test-file.txt)')
+        dirView2 = treeView.root.entries.find('.directory:contains(test-dir2)')
         dirView2.expand()
-        fileView2 = treeView.find('.file:contains(test-file2.txt)').view()
-        fileView3 = treeView.find('.file:contains(test-file3.txt)').view()
+        fileView2 = treeView.find('.file:contains(test-file2.txt)')
+        fileView3 = treeView.find('.file:contains(test-file3.txt)')

     describe "tree-view:copy", ->
       LocalStorage = window.localStorage
@@ -1237,7 +1241,7 @@

         runs ->
           treeView.trigger "tree-view:add-file"
-          addDialog = atom.workspaceView.find(".tree-view-dialog").view()
+          addDialog = atom.workspaceView.find(".tree-view-dialog")

       describe "when a file is selected", ->
         it "opens an add dialog with the file's current directory path populated", ->
@@ -1317,7 +1321,7 @@
           addDialog.cancel()
           dirView.click()
           treeView.trigger "tree-view:add-file"
-          addDialog = atom.workspaceView.find(".tree-view-dialog").view()
+          addDialog = atom.workspaceView.find(".tree-view-dialog")

           expect(addDialog).toExist()
           expect(addDialog.promptText.text()).toBeTruthy()
@@ -1331,7 +1335,7 @@
           addDialog.cancel()
           treeView.root.click()
           treeView.trigger "tree-view:add-file"
-          addDialog = atom.workspaceView.find(".tree-view-dialog").view()
+          addDialog = atom.workspaceView.find(".tree-view-dialog")

           expect(addDialog.miniEditor.getText().length).toBe 0

@@ -1342,7 +1346,7 @@
           treeView.root.removeClass('selected')
           expect(treeView.selectedEntry()).toBeUndefined()
           treeView.trigger "tree-view:add-file"
-          addDialog = atom.workspaceView.find(".tree-view-dialog").view()
+          addDialog = atom.workspaceView.find(".tree-view-dialog")

           expect(addDialog.miniEditor.getText().length).toBe 0

@@ -1357,7 +1361,7 @@

         runs ->
           treeView.trigger "tree-view:add-folder"
-          addDialog = atom.workspaceView.find(".tree-view-dialog").view()
+          addDialog = atom.workspaceView.find(".tree-view-dialog")

       describe "when a file is selected", ->
         it "opens an add dialog with the file's current directory path populated", ->
@@ -1442,7 +1446,7 @@

           runs ->
             treeView.trigger "tree-view:move"
-            moveDialog = atom.workspaceView.find(".tree-view-dialog").view()
+            moveDialog = atom.workspaceView.find(".tree-view-dialog")

         afterEach ->
           waits 50 # The move specs cause too many false positives because of their async nature, so wait a little bit before we cleanup
@@ -1469,10 +1473,10 @@
               expect(moveDialog.parent()).not.toExist()

               waitsFor "tree view to update", ->
-                treeView.root.find('> .entries > .file:contains(renamed-test-file.txt)').length > 0
+                root.find('> .entries > .file:contains(renamed-test-file.txt)').length > 0

               runs ->
-                dirView = treeView.root.entries.find('.directory:contains(test-dir)').view()
+                dirView = treeView.root.entries.find('.directory:contains(test-dir)')
                 dirView.expand()
                 expect(dirView.entries.children().length).toBe 0

@@ -1484,7 +1488,7 @@
               moveDialog.trigger 'core:confirm'

               waitsFor "tree view to update", ->
-                treeView.root.find('> .entries > .directory:contains(new)').length > 0
+                root.find('> .entries > .directory:contains(new)').length > 0

               runs ->
                 expect(fs.existsSync(newPath)).toBeTruthy()
@@ -1525,14 +1529,14 @@
           fs.writeFileSync(dotFilePath, "dot")
           dirView.collapse()
           dirView.expand()
-          dotFileView = treeView.find('.file:contains(.dotfile)').view()
+          dotFileView = treeView.find('.file:contains(.dotfile)')

           waitsForFileToOpen ->
             dotFileView.click()

           runs ->
             treeView.trigger "tree-view:move"
-            moveDialog = atom.workspaceView.find(".tree-view-dialog").view()
+            moveDialog = atom.workspaceView.find(".tree-view-dialog")

         it "selects the entire file name", ->
           expect(moveDialog).toExist()
@@ -1543,7 +1547,7 @@
         it "doesn't display the move dialog", ->
           treeView.root.click()
           treeView.trigger "tree-view:move"
-          expect(atom.workspaceView.find(".tree-view-dialog").view()).not.toExist()
+          expect(atom.workspaceView.find(".tree-view-dialog")).not.toExist()

     describe "tree-view:duplicate", ->
       describe "when a file is selected", ->
@@ -1557,7 +1561,7 @@

           runs ->
             treeView.trigger "tree-view:duplicate"
-            copyDialog = atom.workspaceView.find(".tree-view-dialog").view()
+            copyDialog = atom.workspaceView.find(".tree-view-dialog")

         afterEach ->
           waits 50 # The copy specs cause too many false positives because of their async nature, so wait a little bit before we cleanup
@@ -1581,14 +1585,14 @@
                 copyDialog.trigger 'core:confirm'

               waitsFor "tree view to update", ->
-                treeView.root.find('> .entries > .file:contains(duplicated-test-file.txt)').length > 0
+                root.find('> .entries > .file:contains(duplicated-test-file.txt)').length > 0

               runs ->
                 treeView.trigger "tree-view:duplicate"
                 expect(fs.existsSync(newPath)).toBeTruthy()
                 expect(fs.existsSync(filePath)).toBeTruthy()
                 expect(copyDialog.parent()).not.toExist()
-                dirView = treeView.root.entries.find('.directory:contains(test-dir)').view()
+                dirView = treeView.root.entries.find('.directory:contains(test-dir)')
                 dirView.expand()
                 expect(dirView.entries.children().length).toBe 1
                 expect(atom.workspace.getActiveEditor().getPath()).toBe(newPath)
@@ -1602,7 +1606,7 @@
                 copyDialog.trigger 'core:confirm'

               waitsFor "tree view to update", ->
-                treeView.root.find('> .entries > .directory:contains(new)').length > 0
+                root.find('> .entries > .directory:contains(new)').length > 0

               waitsFor "new path to exist", -> fs.existsSync(newPath)

@@ -1645,14 +1649,14 @@
           fs.writeFileSync(dotFilePath, "dot")
           dirView.collapse()
           dirView.expand()
-          dotFileView = treeView.find('.file:contains(.dotfile)').view()
+          dotFileView = treeView.find('.file:contains(.dotfile)')

           waitsForFileToOpen ->
             dotFileView.click()

           runs ->
             treeView.trigger "tree-view:duplicate"
-            copyDialog = atom.workspaceView.find(".tree-view-dialog").view()
+            copyDialog = atom.workspaceView.find(".tree-view-dialog")

         it "selects the entire file name", ->
           expect(copyDialog).toExist()
@@ -1663,7 +1667,7 @@
         it "doesn't display the copy dialog", ->
           treeView.root.click()
           treeView.trigger "tree-view:duplicate"
-          expect(atom.workspaceView.find(".tree-view-dialog").view()).not.toExist()
+          expect(atom.workspaceView.find(".tree-view-dialog")).not.toExist()

       describe "when the editor has focus", ->
         copyDialog = null
@@ -1675,7 +1679,7 @@
           runs ->
             editorView = atom.workspaceView.getActiveView()
             editorView.trigger "tree-view:duplicate"
-            copyDialog = atom.workspaceView.find(".tree-view-dialog").view()
+            copyDialog = atom.workspaceView.find(".tree-view-dialog")

         it "duplicates the current file", ->
           expect(copyDialog.miniEditor.getText()).toBe('tree-view.js')
@@ -1685,7 +1689,7 @@
         spyOn(atom, 'confirm')
         atom.workspaceView.attachToDom()
         treeView.show()
-        treeView.root.view().click()
+        treeView.root.click()
         treeView.trigger 'tree-view:remove'

         args = atom.confirm.mostRecentCall.args[0]
@@ -1820,7 +1824,7 @@
       atom.project.getRepo().getPathStatus(modifiedFile)

       treeView.updateRoot()
-      treeView.root.entries.find('.directory:contains(dir)').view().expand()
+      treeView.root.entries.find('.directory:contains(dir)').expand()

     describe "when the project is the repository root", ->
       it "adds a custom style", ->
@@ -1828,7 +1832,7 @@

     describe "when a file is modified", ->
       it "adds a custom style", ->
-        treeView.root.entries.find('.directory:contains(dir)').view().expand()
+        treeView.root.entries.find('.directory:contains(dir)').expand()
         expect(treeView.find('.file:contains(b.txt)')).toHaveClass 'status-modified'

     describe "when a directory if modified", ->
@@ -1837,7 +1841,7 @@

     describe "when a file is new", ->
       it "adds a custom style", ->
-        treeView.root.entries.find('.directory:contains(dir2)').view().expand()
+        treeView.root.entries.find('.directory:contains(dir2)').expand()
         expect(treeView.find('.file:contains(new2)')).toHaveClass 'status-added'

     describe "when a directory is new", ->
@@ -1886,12 +1890,12 @@

       runs ->
         atom.workspaceView.trigger 'tree-view:toggle'
-        treeView = atom.workspaceView.find(".tree-view").view()
-        dirView = treeView.root.entries.find('.directory:contains(test-dir)').view()
+        treeView = atom.workspaceView.find(".tree-view")
+        dirView = treeView.root.entries.find('.directory:contains(test-dir)')
         dirView.expand()
-        fileView1 = treeView.find('.file:contains(test-file1.txt)').view()
-        fileView2 = treeView.find('.file:contains(test-file2.txt)').view()
-        fileView3 = treeView.find('.file:contains(test-file3.txt)').view()
+        fileView1 = treeView.find('.file:contains(test-file1.txt)')
+        fileView2 = treeView.find('.file:contains(test-file2.txt)')
+        fileView3 = treeView.find('.file:contains(test-file3.txt)')

     describe 'selecting multiple items', ->
       it 'switches the contextual menu to muli-select mode', ->


Add failing spec for spawn error

Add failing spec for spawn error

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -2393,3 +2393,14 @@
         element.innerText

       expect(gammaEntries).toEqual(["delta.txt", "epsilon.txt", "theta"])
+
+  describe "showSelectedEntryInFileManager()", ->
+    it "spawns a command to show the file in the OS file manager", ->
+      spyOn(atom, 'confirm')
+      spyOn(treeView, 'fileManagerCommandForPath').andReturn
+        command: '/this/command/does/not/exist'
+        label: 'Finder'
+        args: ['foo']
+
+      treeView.showSelectedEntryInFileManager()
+      expect(atom.confirm.callCount).toBe 1


remove require for Disposable that is no longer needed

--- spec/workspace-spec.coffee
+++ spec/workspace-spec.coffee
@@ -1,6 +1,5 @@
 path = require 'path'
 temp = require 'temp'
-{Disposable} = require 'event-kit'
 Workspace = require '../src/workspace'
 Pane = require '../src/pane'
 {View} = require '../src/space-pen-extensions'


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -154,84 +154,84 @@
           console.log 'ts: d', d
           d.data.label
         ).each((d, i) ->
-          # Move all calculations into the each function.
-          # Position values are stored in the data object
-          # so can be accessed later when drawing the line
-          ### calculate the position of the center marker ###
-          a = (d.startAngle + d.endAngle) / 2
-          #trig functions adjusted to use the angle relative
-          #to the "12 o'clock" vector:
-          #console.log 'ts: a', a
-          #d.cx = Math.sin(a) * (radius - 75)
-          #d.cy = -Math.cos(a) * (radius - 75)
-          d.cx = Math.sin(a) * (radiuso - (radiuso - radiusi) / 2)
-          d.cy = -Math.cos(a) * (radiuso - (radiuso - radiusi) / 2)
-          ### calculate the default position for the label,
-             so that the middle of the label is centered in the arc
-          ###
-          bbox = @getBBox()
-          #bbox.width and bbox.height will
-          #describe the size of the label text
-          labelRadius = radius - 20
-          #labelRadius = radiuso * 1.3
-          d.x = Math.sin(a) * labelRadius
-          d.l = d.x - bbox.width / 2 - 2
-          d.r = d.x + bbox.width / 2 + 2
-          #d.y = -Math.cos(a) * (radius - 20)
-          d.y = -Math.cos(a) * (labelRadius)
-          d.b = d.oy = d.y + 5
-          d.t = d.y - bbox.height - 5
-          #console.log 'ts: d', d
-          ### check whether the default position
-             overlaps any other labels
-          ###
-          conflicts = []
-          labelLayout.visit (node, x1, y1, x2, y2) ->
-            #recurse down the tree, adding any overlapping
-            #node is the node in the quadtree,
-            #node.point is the value that we added to the tree
-            #x1,y1,x2,y2 are the bounds of the rectangle that
-            #this node covers
-            if x1 > d.r + maxLabelWidth / 2 or x2 < d.l - maxLabelWidth / 2 or y1 > d.b + maxLabelHeight / 2 or y2 < d.t - maxLabelHeight / 2
-              return true #don't bother visiting children or checking this node
-            p = node.point
-            v = false
-            h = false
-            if p
-              #p is defined, i.e., there is a value stored in this node
-              h = p.l > d.l and p.l <= d.r or p.r > d.l and p.r <= d.r or p.l < d.l and p.r >= d.r
-              #horizontal conflict
-              v = p.t > d.t and p.t <= d.b or p.b > d.t and p.b <= d.b or p.t < d.t and p.b >= d.b
-              #vertical conflict
-              if h and v
-                conflicts.push p
-              #add to conflict list
+            # Move all calculations into the each function.
+            # Position values are stored in the data object
+            # so can be accessed later when drawing the line
+            ### calculate the position of the center marker ###
+            a = (d.startAngle + d.endAngle) / 2
+            #trig functions adjusted to use the angle relative
+            #to the "12 o'clock" vector:
+            #console.log 'ts: a', a
+            #d.cx = Math.sin(a) * (radius - 75)
+            #d.cy = -Math.cos(a) * (radius - 75)
+            d.cx = Math.sin(a) * (radiuso - (radiuso - radiusi) / 2)
+            d.cy = -Math.cos(a) * (radiuso - (radiuso - radiusi) / 2)
+            ### calculate the default position for the label,
+               so that the middle of the label is centered in the arc
+            ###
+            bbox = @getBBox()
+            #bbox.width and bbox.height will
+            #describe the size of the label text
+            labelRadius = radius - 20
+            #labelRadius = radiuso * 1.3
+            d.x = Math.sin(a) * labelRadius
+            d.l = d.x - bbox.width / 2 - 2
+            d.r = d.x + bbox.width / 2 + 2
+            #d.y = -Math.cos(a) * (radius - 20)
+            d.y = -Math.cos(a) * (labelRadius)
+            d.b = d.oy = d.y + 5
+            d.t = d.y - bbox.height - 5
+            #console.log 'ts: d', d
+            ### check whether the default position
+               overlaps any other labels
+            ###
+            conflicts = []
+            labelLayout.visit (node, x1, y1, x2, y2) ->
+              #recurse down the tree, adding any overlapping
+              #node is the node in the quadtree,
+              #node.point is the value that we added to the tree
+              #x1,y1,x2,y2 are the bounds of the rectangle that
+              #this node covers
+              if x1 > d.r + maxLabelWidth / 2 or x2 < d.l - maxLabelWidth / 2 or y1 > d.b + maxLabelHeight / 2 or y2 < d.t - maxLabelHeight / 2
+                return true #don't bother visiting children or checking this node
+              p = node.point
+              v = false
+              h = false
+              if p
+                #p is defined, i.e., there is a value stored in this node
+                h = p.l > d.l and p.l <= d.r or p.r > d.l and p.r <= d.r or p.l < d.l and p.r >= d.r
+                #horizontal conflict
+                v = p.t > d.t and p.t <= d.b or p.b > d.t and p.b <= d.b or p.t < d.t and p.b >= d.b
+                #vertical conflict
+                if h and v
+                  conflicts.push p
+                #add to conflict list
+              return
+            if conflicts.length
+              console.log d, ' conflicts with ', conflicts
+              rightEdge = d3.max(conflicts, (d2) ->
+                `var maxLabelHeight`
+                `var maxLabelWidth`
+                d2.r
+              )
+              d.l = rightEdge
+              d.x = d.l + bbox.width / 2 + 5
+              d.r = d.l + bbox.width + 10
+            else
+              console.log 'no conflicts for ', d
+            ### add this label to the quadtree, so it will show up as a conflict
+               for future labels.
+            ###
+            labelLayout.add d
+            maxLabelWidth = Math.max(maxLabelWidth, bbox.width + 10)
+            maxLabelHeight = Math.max(maxLabelHeight, bbox.height + 10)
             return
-          if conflicts.length
-            console.log d, ' conflicts with ', conflicts
-            rightEdge = d3.max(conflicts, (d2) ->
-              `var maxLabelHeight`
-              `var maxLabelWidth`
-              d2.r
-            )
-            d.l = rightEdge
-            d.x = d.l + bbox.width / 2 + 5
-            d.r = d.l + bbox.width + 10
-          else
-            console.log 'no conflicts for ', d
-          ### add this label to the quadtree, so it will show up as a conflict
-             for future labels.
-          ###
-          labelLayout.add d
-          maxLabelWidth = Math.max(maxLabelWidth, bbox.width + 10)
-          maxLabelHeight = Math.max(maxLabelHeight, bbox.height + 10)
-          return
         )
         .transition()
         .attr('x', (d) ->
-          d.x
+            d.x
         ).attr 'y', (d) ->
-          d.y
+            d.y
         pointers = pointerGroup.selectAll('path.pointer').data(piedata)
         pointers.enter()
             .append('path')
@@ -241,12 +241,9 @@
             .attr('marker-end', 'url(#circ)')
         pointers.exit().remove()
         pointers.transition().attr 'd', (d) ->
-          console.log 'ts: d', d
-          if d.cx > d.l
-            console.log 'if d.cx > d.l','M' + (d.l + 2) + ',' + d.b + 'L' + (d.r - 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
-            'M' + (d.l + 2) + ',' + d.b + 'L' + (d.r - 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
-          else
-            console.log 'else', 'M' + (d.r - 2) + ',' + d.b + 'L' + (d.l + 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
-            'M' + (d.r - 2) + ',' + d.b + 'L' + (d.l + 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
+            if d.cx > d.l
+                'M' + (d.l + 2) + ',' + d.b + 'L' + (d.r - 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
+            else
+                'M' + (d.r - 2) + ',' + d.b + 'L' + (d.l + 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
         oldPieData = piedata
         return


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -3,7 +3,13 @@
     @accessor 'data'

     ready: ->
-        @width = 750
+        # Config values here
+        @width = 750                            # Width of the SVG area
+        @height = 400                           # Height of the SVG area
+        @radius = Math.min(width, height) / 2   # Calculated min dimension of the SVG area
+        @radiuso = 130                          # Outer radius of the pie
+        @radiusi = 65                           # Inner radius of the pie (zero = pie, non-zero = donut)
+        @color = d3.scale.category20()          # Color scale for pie slices

     onData: (data) ->
         #$(@node).fadeOut().fadeIn()
@@ -22,9 +28,9 @@
         width = @width
         height = 400
         radius = Math.min(width, height) / 2
-        radiuso = 130
-        radiusi = 65
-        color = d3.scale.category20()
+        radiuso = @radiuso
+        radiusi = @radiusi
+        color = @color
         pie = d3.layout.pie().value((d) -> d.value)
         arc = d3.svg.arc().innerRadius(radiusi).outerRadius(radiuso)
         svg = d3.select(@node).append('svg').attr('width', width).attr('height', height).append('g').attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')')


Agent pie: start/end angle pie transition work

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -9,11 +9,14 @@
         @instanceDataId = $(@node).attr('data-id')
         #console.log('@instanceDataId',@instanceDataId)
         @oldPieData[@instanceDataId] = ''
+        window.oldPieData = window.oldPieData or {}
+        windows.oldPieData[@instanceDataId] = windows.oldPieData[@instanceDataId] or {}

     onData: (data) ->
-        console.log('testData',@testData)
-        @testData = @testData + 1
-        console.log('testData',@testData)
+        #console.log('testData',@testData)
+        #@testData = @testData + 1
+        #console.log('testData',@testData)
+        console.log('window.oldPieData',window.oldPieData)

         #$(@node).fadeOut().fadeIn()
         @container = $(@node).parent()
@@ -157,20 +160,20 @@
         if totalLabel.empty()
             totalLabel = svg.append('g').attr('class', 'totalLabel')

-        console.log('@oldPieData before the tween',@oldPieData[@instanceDataId] ? null)
-        window.oldPieData[@instanceDataId] = @oldPieData[@instanceDataId] ? null
-
+        #console.log('@oldPieData before the tween',@oldPieData[@instanceDataId] ? null)
+        #window.oldPieData[@instanceDataId] = @oldPieData[@instanceDataId] ? null
+        myInstanceDataId = @instanceDataId
         path = pathGroup.selectAll('path.pie').data(piedata)
         path.enter().append('path').attr('class', 'pie').attr('fill', (d, i) -> color i)

         path.transition().duration(1000).attrTween('d', (d,i) ->
-            theOldDataInPie = window.oldPieData[@instanceDataId] ? piedata
+            theOldDataInPie = window.oldPieData[myInstanceDataId] ? piedata
             console.log('theOldDataInPie in the tween',theOldDataInPie)
-            console.log('@oldPieData in the tween',@oldPieData[@instanceDataId])
-            console.log('window.oldPieData in the tween',window.oldPieData[@instanceDataId])
-            @testData = @testData + 1000
-            tD = @testData + 1000
-            console.log('tD in the tween',tD)
+            #console.log('@oldPieData in the tween',@oldPieData[@instanceDataId])
+            console.log('window.oldPieData in the tween',window.oldPieData[myInstanceDataId])
+            #@testData = @testData + 1000
+            #tD = @testData + 1000
+            #console.log('tD in the tween',tD)
             # Interpolate the arcs in data space
             s0 = undefined
             e0 = undefined
@@ -390,5 +393,5 @@
             .attr('font-size',(if typeof totalTickets isnt 'string' then radiusi + 'px' else '2em'))
             .style('opacity', 1)

-        @oldPieData[@instanceDataId] = piedata
+        windows.oldPieData[@instanceDataId] = piedata
         return


Add suffix to status bar count

Add suffix to status bar count

--- lib/deprecation-cop-status-bar-view.coffee
+++ lib/deprecation-cop-status-bar-view.coffee
@@ -57,7 +57,7 @@
     return if @lastLength == length

     @lastLength = length
-    @deprecationNumber.text(length)
+    @deprecationNumber.text("#{_.pluralize(length, 'deprecation')}!")
     @toolTipDisposable?.dispose()
     @toolTipDisposable = atom.tooltips.add @element, title: "#{_.pluralize(length, 'call')} to deprecated methods"


scroll to bottom on switch conv

scroll to bottom on switch conv

--- src/ui/views/messages.coffee
+++ src/ui/views/messages.coffee
@@ -65,6 +65,7 @@
     subtree:true

 firstRender = true
+lastConv = null # to detect conv switching

 module.exports = view (models) ->
     # mutation events kicks in after first render
@@ -101,6 +102,10 @@
                                     format e.chat_message?.message_content
                                     loadImages conv_id, e.chat_message?.message_content

+    # focus when switching convs
+    if lastConv != models.viewstate.selectedConv
+        lastConv = models.viewstate.selectedConv
+        scrollToBottom()

 # when there's mutation, we scroll to bottom in case we already are at bottom
 onMutate = (atbottom) ->


add async call

add async call

--- lib/mocha-phantomjs.coffee
+++ lib/mocha-phantomjs.coffee
@@ -53,7 +53,8 @@
           failures: 0
           ended: false
           started: false
-          run: ->
+          callback: []
+          run: () ->
             mochaPhantomJS.started = true
             window.callPhantom 'mochaPhantomJS.run': true
       , system.env
@@ -112,11 +113,15 @@
   runner: (reporter) ->
     try
       mocha.setup reporter: reporter
-      mochaPhantomJS.runner = mocha.run()
+
+      mochaPhantomJS.callback.forEach (cb) ->
+        mochaPhantomJS.runner = mocha.run(cb)
+
       if mochaPhantomJS.runner
         cleanup = ->
           mochaPhantomJS.failures = mochaPhantomJS.runner.failures
           mochaPhantomJS.ended = true
+          mochaPhantomJS.callback = []
         if mochaPhantomJS.runner?.stats?.end
           cleanup()
         else


Update atom/config.cson

Update atom/config.cson

--- atom/config.cson
+++ atom/config.cson
@@ -1,11 +1,11 @@
 'editor':
   'fontSize': 13
+  'fontFamily': 'Menlo'
   'showIndentGuide': true
-  'fontFamily': 'Menlo'
 'core':
   'themes': [
     'atom-dark-ui'
-    'atom-dark-syntax'
+    'monokai'
   ]
   'ignoredNames': [
     '.git'


update atom config

update atom config

--- atom/config.cson
+++ atom/config.cson
@@ -186,6 +186,8 @@
     showIcons: true
     usePreviewTabs: true
   term3: {}
+  "test-status":
+    autorun: false
   "theme-switcher":
     syntaxThemeOne: "tomorrow-ya"
     syntaxThemeTwo: "today"


Update config.cson for Atom

Update config.cson for Atom

--- atom/config.cson
+++ atom/config.cson
@@ -54,10 +54,8 @@
     directory: "/Users/j/.notes"
   nvatom:
     directory: "/Users/j/.notes"
-  "one-dark-ui":
-    layoutMode: "Compact"
-  "one-light-ui":
-    layoutMode: "Compact"
+  "one-dark-ui": {}
+  "one-light-ui": {}
   "outdoor-theme":
     lightLevelToSwitch: 5904360
   "spell-check":
@@ -72,6 +70,7 @@
     leftEditorColor: "red"
     rightEditorColor: "green"
     syncHorizontalScroll: true
+  "status-bar": {}
   tabs:
     enableVcsColoring: true
   welcome:


Update prettier settings - Atom - don't change .md files

--- atom/config.cson
+++ atom/config.cson
@@ -57,6 +57,7 @@
       enabled: true
       excludedGlobs: [
         ".scss"
+        ".md"
       ]
     prettierOptions:
       singleQuote: true


show indent lines

show indent lines

--- atom/config.cson
+++ atom/config.cson
@@ -11,3 +11,4 @@
   editor:
     invisibles: {}
     fontFamily: "Source Code Pro"
+    showIndentGuide: true


Update specs for new class names

Update specs for new class names

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -957,20 +957,20 @@
     describe "when a file is modified", ->
       it "adds a custom style", ->
         treeView.root.entries.find('.directory:contains(dir1)').view().expand()
-        expect(treeView.find('.file:contains(file1)')).toHaveClass 'modified'
+        expect(treeView.find('.file:contains(file1)')).toHaveClass 'status-modified'

     describe "when a directory if modified", ->
       it "adds a custom style", ->
-        expect(treeView.find('.directory:contains(dir1)')).toHaveClass 'modified'
+        expect(treeView.find('.directory:contains(dir1)')).toHaveClass 'status-modified'

     describe "when a file is new", ->
       it "adds a custom style", ->
-        expect(treeView.find('.file:contains(.gitignore)')).toHaveClass 'new'
+        expect(treeView.find('.file:contains(.gitignore)')).toHaveClass 'status-added'

     describe "when a directory is new", ->
       it "adds a custom style", ->
-        expect(treeView.find('.directory:contains(dir2)')).toHaveClass 'new'
+        expect(treeView.find('.directory:contains(dir2)')).toHaveClass 'status-added'

     describe "when a file is ignored", ->
       it "adds a custom style", ->
-        expect(treeView.find('.file:contains(tree-view.js)')).toHaveClass 'ignored'
+        expect(treeView.find('.file:contains(tree-view.js)')).toHaveClass 'status-ignored'


Add type escalation

Add type escalation

--- lib/symbol-provider.coffee
+++ lib/symbol-provider.coffee
@@ -16,10 +16,10 @@
   config: null
   defaultConfig:
     class:
-      selector: '.class.name'
+      selector: '.class.name, .inherited-class'
       priority: 3
     function:
-      selector: '.function'
+      selector: '.function.name'
       priority: 2
     variable:
       selector: '.variable'
@@ -67,7 +67,10 @@
     for config in allConfig
       for type, options of config
         @config[type] = _.clone(options)
-        [@config[type].selector] = Selector.create(options.selector) if options.selector?
+        @config[type].selectors = Selector.create(options.selector) if options.selector?
+        @config[type].selectors ?= []
+        @config[type].priority ?= 1
+        @config[type].wordRegex ?= @wordRegex

     return

@@ -168,17 +171,48 @@
     @wordList = wordList

   getSymbolsFromEditor: (editor, minimumWordLength) ->
+    # Warning: displayBuffer and tokenizedBuffer are private APIs. Please do not
+    # copy into your own package. If you do, be prepared to have it break
+    # without warning.
     tokenizedLines = editor.displayBuffer.tokenizedBuffer.tokenizedLines
-    matchedTokens = []
+
+    symbolTypes = {}
+    matchedSymbols = []
+
+    # Handle the case where a symbol is a variable in some cases and, say, a
+    # class in others. We want all symbols in the file to have the same type.
+    # e.g. `class` types are higher priority than `variables`
+    cacheSymbolType = (word, currentType) =>
+      word = getWordKey(word)
+      return symbolTypes[word] = currentType unless symbolTypes[word]?
+      currentTypePriority = @config[currentType].priority
+      cachedTypePriority = @config[symbolTypes[word]].priority
+      symbolTypes[word] = currentType if currentTypePriority > cachedTypePriority
+
+    getWordKey = (word) ->
+       # some words are reserved, like 'constructor' :/
+      word + '$$'
+
+    normalizeTypesForAllSymbols = ->
+      for symbol in matchedSymbols
+        wordKey = getWordKey(symbol.word)
+        symbol.type = symbolTypes[wordKey] if symbolTypes[wordKey]?
+      return
+
     for {tokens}, bufferRow in tokenizedLines
       for token in tokens
         scopes = @cssSelectorFromScopes(token.scopes)
         for type, options of @config
-          if options.selector?.matches(scopes) and matches = token.value.match(@wordRegex)
-            for word in matches
-              matchedTokens.push {type, word, bufferRow} if word.length >= minimumWordLength
-
-    matchedTokens
+          for selector in options.selectors
+            if selector.matches(scopes) and matches = token.value.match(options.wordRegex)
+              for word in matches
+                if word.length >= minimumWordLength
+                  matchedSymbols.push {type, word, bufferRow, scopes}
+                  cacheSymbolType(word, type)
+              break
+
+    normalizeTypesForAllSymbols()
+    matchedSymbols

   cssSelectorFromScopes: (scopes) ->
     selector = ''


updated tests for creating a mirror to be in line with 'create new'+save approach instead of addMirror(xyz)

this is more natural for an angular-based implementation

Signed-off-by: Dominik Richter <b7323add517e187efe0283b76511b7ed762bfe13@gmail.com>

--- test/spec/controllers/main.coffee
+++ test/spec/controllers/main.coffee
@@ -291,8 +291,16 @@
   ## Querying Mirrors

   it 'should be able to [C]reate a new mirror', () ->
-    callResponse @httpBackend, '/v1/mirrors', 'POST', 201, allMirrors1[0], () -> scope.addMirror(allMirrors1[0])
-    expect(scope.mirrors[0]).toBe(allMirrors1[0])
+    # when you create a new mirror it should add a new position to the list to the front
+    obj = scope.newMirror()
+    expect(obj).toBe(scope.mirrors[0])
+    expect(obj.id).toBe(undefined)
+    expect(obj.saved).toBe(false)
+    # when you click on save, it should issue an post request to the server to indicate a new element
+    callResponse @httpBackend, '/v1/mirrors', 'POST', 201, allMirrors1[0], () -> scope.saveMirror(obj)
+    # make sure nothing is changed if the call was successful
+    expect(obj.id).not.toBe(undefined)
+    expect(obj.saved).toBe(true)

   it 'should [R]ead all mirrors the server has available', () ->
     paginateResponse @httpBackend, '/v1/mirrors', allMirrors1, () -> scope.fetchMirrors()


misc codestyle

misc codestyle

--- client/app/views/config_applications.coffee
+++ client/app/views/config_applications.coffee
@@ -8,7 +8,7 @@
 UpdateStackModal = require './update_stack_modal'
 AppsCollection = require '../collections/application'

-module.exports = class exports.ConfigApplicationsView extends BaseView
+module.exports = class ConfigApplicationsView extends BaseView
     id: 'config-applications-view'
     template: require 'templates/config_applications'


変数名かぶり修正

変数名かぶり修正

--- lib/pig-media-server/views/movie.coffee
+++ lib/pig-media-server/views/movie.coffee
@@ -95,7 +95,7 @@
     next_loop()
   ,200

-next = ->
+next_movie = ->
   if $('video')[0]
     v = document.querySelector 'video'
     key = $(v).attr 'key'
@@ -162,7 +162,7 @@
 key_func_t = -> tweet() if $('video')[0]
 key_func_c = -> tweet_with_comment() if $('video')[0]
 key_func_p = -> pause() if $('video')[0]
-key_func_n = -> next()
+key_func_n = -> next_movie()

 remote = ->
   $('a.remote').click ->


.

Former-commit-id: 69cc726c7b8995965b7baf34fadc30761d3d3b15 [formerly 41c0f889715076b7c1c872556ae748979560f54d] [formerly ad8eb50c604e2b30a2c6e143ecf76274baad3796 [formerly 36080f9a9bd68d1ec665f63145f1f0bc719bde6f]] [formerly 096a38d22d3238269a38d1ecb09ccd1dc7db3237 [formerly 8e78111565a296219bc3c2e72b69bbeeeab8f63f] [formerly 95e7944791be43940f3e2d743d53658f72644708 [formerly 95e7944791be43940f3e2d743d53658f72644708 [formerly 6fd354b4497295cb026902f0806ec719f74376d5]]]]
Former-commit-id: 21da4abba64784dcc96829360d2aede0fe97437a [formerly ec622af38534e44b86b8a53ac62ddacd1194fc60] [formerly d97602f9318ebd396398860d2780fb9f897ffa9f [formerly 67f9c630b3b4af5dc4ef2447e28ae9556f0dad88]]
Former-commit-id: 28ca9cd39064b56ea2be13a7a11bce6f77e24746 [formerly 54c30569c54f8e11f54cb6231f2e3dfe8af04e7c]
Former-commit-id: cff8f6535c3d9caea6f24fde2080db820da2d1ed
Former-commit-id: 029782adb40738361d1d03e6ba11037f38a311ac

--- @client/watch_star.coffee
+++ @client/watch_star.coffee
@@ -13,9 +13,9 @@

     label = @props.label || (watching) ->
       if watching
-        "Stop watching this proposal"
+        "Stop getting notifications"
       else
-        "Watch this proposal"
+        "Get notifications about this topic"

     watching = current_user.subscriptions[proposal.key] == 'watched'


JNewStatus: special case for koding group removed

--- workers/social/lib/social/models/messages/newstatusupdate/index.coffee
+++ workers/social/lib/social/models/messages/newstatusupdate/index.coffee
@@ -200,10 +200,6 @@
       if err then return callback err
       unless group then return callback {error: "Group not found"}

-      # this is not a security hole
-      # everybody can read koding activity feed
-      return callback null, group if groupName is "koding"
-
       # if group is not koding check for security
       {delegate} = client.connection
       return callback {error: "Request not valid"} unless delegate


Activity: add group selector into group activity fetcher method

--- workers/social/lib/social/models/messages/newstatusupdate/index.coffee
+++ workers/social/lib/social/models/messages/newstatusupdate/index.coffee
@@ -225,7 +225,10 @@

       @getExemptUserIdsIfNeeded client, options, (err, ids)=>
         return callback err  if err
-        selector = 'meta.createdAt': $lt: to
+        selector =
+          'meta.createdAt': $lt: to
+          group: group.slug
+
         # remove exempts from result set
         selector.originId = $nin : ids if ids.length > 0


Split ship item

Split ship item

--- views/components/ship-parts/panebody.cjsx
+++ views/components/ship-parts/panebody.cjsx
@@ -62,6 +62,7 @@
     {$ships, $shipTypes, _ships} = window
     ship = _ships[shipId]
     shipInfo = $ships[ship.api_ship_id]
+    @id = shipId
     @type = $shipTypes[shipInfo.api_stype].api_name
     @name = shipInfo.api_name
     @lv = ship.api_lv
@@ -83,6 +84,81 @@
         id: itemId
         onslot: ship.api_onslot[i]
         maxeq: ship.api_maxeq[i]
+
+ShipRow = React.createClass
+  shouldComponentUpdate: (nextProps, nextState) ->
+    not _.isEqual nextProps, @props
+  render: ->
+

+

+

+

+

+
+                Lv. {@props.shipData.lv}
+
+
+                {i18n.resources.__ @props.shipData.type}
+
+

+
+              {i18n.resources.__ @props.shipData.name}
+
+
+              Next. {@props.shipData.nextEXP}
+
+

+          {
+            shipStat =
+

+

+
+                    {@props.shipData.nowHp} / {@props.shipData.maxHp}
+
+

+                    <StatusLabel label={@props.label}/>
+

+

+
+                      ★{@props.shipData.cond}
+
+

+

+
+                  <ProgressBar bsStyle={getHpStyle @props.shipData.nowHp / @props.shipData.maxHp * 100}
+                               now={@props.shipData.nowHp / @props.shipData.maxHp * 100} />
+
+

+            if @props.shipData.ndockTime
+              <OverlayTrigger show = {@props.shipData.ndockTime} placement='right' overlay={
+                              <Tooltip id="panebody-repair-time-#{@props.key}-#{@props.shipIndex}">
+                                {__ 'Repair Time'}: {resolveTime @props.shipData.ndockTime / 1000}
+                              </Tooltip>}>
+                {shipStat}
+              </OverlayTrigger>
+            else
+              shipStat
+          }
+

+

+
+
+          <OverlayTrigger placement='right' overlay={<Tooltip id="panebody-fuel-#{@props.key}-#{@props.shipIndex}">{@props.shipData.nowFeul} / {@props.shipData.maxFeul}</Tooltip>}>
+            <ProgressBar bsStyle={getMaterialStyle @props.shipData.fuelStatus}
+                         now={@props.shipData.fuelStatus} />
+          </OverlayTrigger>
+
+
+          <OverlayTrigger placement='right' overlay={<Tooltip id="panebody-bull-#{@props.key}-#{@props.shipIndex}">{@props.shipData.nowBull} / {@props.shipData.maxBull}</Tooltip>}>
+            <ProgressBar bsStyle={getMaterialStyle @props.shipData.bullStatus}
+                         now={@props.shipData.bullStatus} />
+          </OverlayTrigger>
+
+
+

+        <Slotitems key={@props.shipIndex} fleet={@props.deckIndex} slots={@props.shipData.slotItems}/>
+

+

 PaneBody = React.createClass
   condDynamicUpdateFlag: false
@@ -167,76 +243,13 @@
         {
           {$shipTypes, _ships} = window
           for shipData, j in @state.ships
-

-

-

-

-

-
-                        Lv. {shipData.lv}
-
-
-                        {i18n.resources.__ shipData.type}
-
-

-
-                      {i18n.resources.__ shipData.name}
-
-
-                      Next. {shipData.nextEXP}
-
-

-                  {
-                    shipStat =
-

-

-
-                            {shipData.nowHp} / {shipData.maxHp}
-
-

-                            <StatusLabel label={@state.label[j]}/>
-

-

-
-                              ★{shipData.cond}
-
-

-

-
-                          <ProgressBar bsStyle={getHpStyle shipData.nowHp / shipData.maxHp * 100}
-                                       now={shipData.nowHp / shipData.maxHp * 100} />
-
-

-                    if shipData.ndockTime
-                      <OverlayTrigger show = {shipData.ndockTime} placement='right' overlay={
-                                      <Tooltip id="panebody-repair-time-#{@props.key}-#{j}">
-                                        {__ 'Repair Time'}: {resolveTime shipData.ndockTime / 1000}
-                                      </Tooltip>}>
-                        {shipStat}
-                      </OverlayTrigger>
-                    else
-                      shipStat
-                  }
-

-

-
-
-                  <OverlayTrigger placement='right' overlay={<Tooltip id="panebody-fuel-#{@props.key}-#{j}">{shipData.nowFeul} / {shipData.maxFeul}</Tooltip>}>
-                    <ProgressBar bsStyle={getMaterialStyle shipData.fuelStatus}
-                                 now={shipData.fuelStatus} />
-                  </OverlayTrigger>
-
-
-                  <OverlayTrigger placement='right' overlay={<Tooltip id="panebody-bull-#{@props.key}-#{j}">{shipData.nowBull} / {shipData.maxBull}</Tooltip>}>
-                    <ProgressBar bsStyle={getMaterialStyle shipData.bullStatus}
-                                 now={shipData.bullStatus} />
-                  </OverlayTrigger>
-
-
-

-                <Slotitems key={j} fleet={@props.deckIndex} slots={shipData.slotItems}/>
-

-

+            <ShipRow
+              key={shipData.id}
+              label={@state.label[j]}
+              shipData={shipData}
+              deckIndex={@props.deckIndex}
+              shipIndex={j}
+            />
         }


fix mistake

fix mistake

--- views/components/ship-parts/topalert.cjsx
+++ views/components/ship-parts/topalert.cjsx
@@ -307,7 +307,7 @@
       if @props.mini

           Lv. {@messages.totalLv}
-          {__ 'Fighter Power'}: {@messages.tyku.min}
+          {__ 'Fighter Power'}: {@messages.tyku.max}
           {__ 'LOS'}: {@messages.saku25a.total}

       else
@@ -317,10 +317,10 @@

               <OverlayTrigger placement='bottom' overlay={
                 <Tooltip id='topalert-FP'>
-                  {__ 'Basic FP'}: {@messages.tyku.min} {__ 'Rank bonuses'}: {@messages.tyku.max}
+                  {__ 'minimum FP'}: {@messages.tyku.min} {__ 'maximum FP'}: {@messages.tyku.max}
                 </Tooltip>
               }>
-                {__ 'Fighter Power'}: {@messages.tyku.min}
+                {__ 'Fighter Power'}: {@messages.tyku.max}
               </OverlayTrigger>


open passphrase pop up on login

open passphrase pop up on login

--- web/js/services/passphrase_service.coffee
+++ web/js/services/passphrase_service.coffee
@@ -37,11 +37,6 @@
 window.App.factory 'PassphraseService', [
   '$uibModal'
   ($uibModal) ->
-    init: ->
-      key = $.jStorage.get 'passphrase', null
-
-      if !key
-        @open()

     open: ->
       modalOpts =


Removed old initmap function in favor of init function and renamed updateDetails to updateChallengeDetails

--- static/js/maproulette.coffee
+++ static/js/maproulette.coffee
@@ -37,7 +37,8 @@

 # Misc variables
 map = undefined
-geojsonLayer = new L.GeoJSON()
+geojsonLayer = null
+

 setDelay = (seconds, func) ->
   ###
@@ -219,7 +220,7 @@
     # display a message saying where we are in the world
     msg locstr

-@getTask = (difficulty, near) ->
+@getInitialTask = (difficulty, near) ->
   ###
   # Gets a new task and challenge and displays it
   ###
@@ -262,7 +263,7 @@
   # location (if supplied)
   ###
     if not currentChallenge? or currentChallenge.slug != challenge
-      updateDetails(challenge)
+      updateChallengeDetails(challenge)
     # In the meantime, we can grab our task, I think...
     if near
       url = "/c/#{currentChallenge.slug}/task?near=#{near}"
@@ -301,43 +302,17 @@
   # The second argument adds the layer at the bottom
   map.addLayer(tileLayer, true)

-initmap = ->
-  ###
-  # Initialize Leaflet map and layers
-  ###
-  map = new L.Map "map"
-  tileLayer = new L.TileLayer(tileUrl, attribution: tileAttrib)
-  map.setView new L.LatLng(40.0, -90.0), 17
-  map.addLayer tileLayer
-  # We need an onEachFeature function to create markers
+addGeoJSONLayer = ->
+  ###
+  # Adds a GeoJSON layer to the map
+  ###
   geojsonLayer = new L.geoJson(null, {
-      onEachFeature: (feature, layer) ->
-        if feature.properties and feature.properties.text
-          layer.bindPopup(feature.properties.text)
-          layer.openPopup()})
+    # We need an onEachFeature function to create markers
+    onEachFeature: (feature, layer) ->
+      if feature.properties and feature.properties.text
+        layer.bindPopup(feature.properties.text)
+        layer.openPopup()})
   map.addLayer geojsonLayer
-
-  # get the first task
-  getTask()
-
-  # add keyboard hooks
-  if enablekeyboardhooks
-    $(document).bind "keydown", (e) ->
-      key = String.fromCharCode(e)
-      switch key.which
-        when "q"
-            nextUp "falsepositive"
-        when "w"
-            nextUp "skip"
-        when "e"
-          openIn('josm')
-        when "r"
-          openIn('potlatch')
-        when "i"
-          openIn('id')
-
-    # Update the counter
-    updateCounter()

 @nextUp = (action) ->
   ###
@@ -465,7 +440,7 @@
     remaining = data.total - data.done
     $("#counter").text remaining

-updateDetails = (challenge) ->
+updateChallengeDetails = (challenge) ->
   ###
   # Use the current challenge metadata to fill in the web page
   ###
@@ -475,24 +450,17 @@
     if data.tileurl? and data.tileurl != tileURL
       tileURL = data.tileurl
       tileAttrib = data.tileasttribution if data.tileattribution?
-      initmap()
+      changeMapLayer(tileURL, tileAttrib)

 @init = ->
   ###
   # Find a challenge and set the map up
   ###
-  # Set up the map
   map = new L.Map "map"
-  osmLayer = new L.TileLayer(tileUrl, attribution: tileAttrib)
+  tileLayer = new L.TileLayer(tileUrl, attribution: tileAttrib)
   map.setView new L.LatLng(40.0, -90.0), 17
-  map.addLayer osmLayer
-  # We need an onEachFeature function to create markers later
-  geojsonLayer = new L.geoJson(null, {
-      onEachFeature: (feature, layer) ->
-        if feature.properties and feature.properties.text
-          layer.bindPopup(feature.properties.text)
-          layer.openPopup()})
-  map.addLayer geojsonLayer
+  map.addLayer tileLayer
+  addGeoJSONLayer()

   # add keyboard hooks
   if enablekeyboardhooks
@@ -510,6 +478,19 @@
         when "i"
           openIn('id')

+  # Set up the map
+  map = new L.Map "map"
+  osmLayer = new L.TileLayer(tileUrl, attribution: tileAttrib)
+  map.setView new L.LatLng(40.0, -90.0), 17
+  map.addLayer osmLayer
+  # We need an onEachFeature function to create markers later
+  geojsonLayer = new L.geoJson(null, {
+      onEachFeature: (feature, layer) ->
+        if feature.properties and feature.properties.text
+          layer.bindPopup(feature.properties.text)
+          layer.openPopup()})
+  map.addLayer geojsonLayer
+
   # Try to grab parameters from the url
   challenge = $(document).getUrlParam("challenge")
   difficulty = $(document).getUrlParam("difficulty")


Properly clear localStorage on logout

Properly clear localStorage on logout

--- website/src/js/models/user_session.coffee
+++ website/src/js/models/user_session.coffee
@@ -26,9 +26,14 @@
     setCurrentUser: (user) =>
       @currentUser = user

-      localStorage.setItem('authToken', user.authToken)
-      localStorage.setItem('firstname', user.firstname)
-      localStorage.setItem('lastname', user.lastname)
+      if user
+        localStorage.setItem('authToken', user.authToken)
+        localStorage.setItem('firstname', user.firstname)
+        localStorage.setItem('lastname', user.lastname)
+      else
+        localStorage.removeItem('authToken')
+        localStorage.removeItem('firstname')
+        localStorage.removeItem('lastname')

     getCurrentUser: =>
       return @currentUser if @currentUser?


Fixed #282 processing snippets

Fixed #282 processing snippets

--- wzk/net/XhrFactory.coffee
+++ wzk/net/XhrFactory.coffee
@@ -68,11 +68,17 @@
   ###
   handleComplete: (e) =>
     xhr = (`/** @type {wzk.net.XhrIo} */`) e.target
-    if xhr.getStatus() isnt 204 and @isJsonResponse xhr
+    if xhr.getStatus() isnt 204
       config = e.target.getConfig()
-      response = (`/** @type {wzk.net.XhrIo} */`) xhr.getResponseJson()
-      @flash.clearAll() if config.flash
-      @applyJsonResponse response, config
+
+      try
+        # we cannot check response type because of IE
+        # snippets must be sent with text/plain instead of application/json
+        response = (`/** @type {wzk.net.XhrIo} */`) xhr.getResponseJson()
+        @flash.clearAll() if config.flash
+        @applyJsonResponse response, config
+      catch error
+        # TODO log error

   ###*
     Applies json response


Popup now uses open class, resolves #250

--- wzk/ui/popup/Popup.coffee
+++ wzk/ui/popup/Popup.coffee
@@ -4,7 +4,7 @@
     @enum {string}
   ###
   @CLS:
-    HIDDEN: 'hidden'
+    OPEN: 'open'

   ###*
     @param {Object} params
@@ -21,10 +21,7 @@
     @override
     @param {Element} element
   ###
-  decorateInternal: (element) ->
-    toggleId = goog.dom.dataset.get element, 'toggle'
-    @toggleElement = @dom.one "##{toggleId}"
-
+  decorateInternal: (@element) ->
     ###
       Do not use ACTION event as propagation must be stopped. If you would stop
       propagation on ACTION event, CLICK event would be fired on body and popup
@@ -36,7 +33,7 @@
     goog.events.listen element, goog.events.EventType.TOUCHSTART, @handleToggle
     goog.events.listen @dom.one('body'), goog.events.EventType.TOUCHSTART, @handleClose

-    super(element)
+    super(@element)

   ###*
     @protected
@@ -61,6 +58,6 @@
   ###
   toggle: ->
     if @shown
-      goog.dom.classes.remove @toggleElement, wzk.ui.popup.Popup.CLS.HIDDEN
+      goog.dom.classes.add @element, wzk.ui.popup.Popup.CLS.OPEN
     else
-      goog.dom.classes.add @toggleElement, wzk.ui.popup.Popup.CLS.HIDDEN
+      goog.dom.classes.remove @element, wzk.ui.popup.Popup.CLS.OPEN


fixed bug with gradientRadius not resetting shadow param now accepts an "offset" sub-prop which is passed to PointLayout allowing full layout features for the shadow's offset. The offset is relative to the center of the object.

--- src/art/engine/elements/fillable_base.coffee
+++ src/art/engine/elements/fillable_base.coffee
@@ -3,7 +3,7 @@
 Canvas = require 'art-canvas'
 Base = require './base'
 {PointLayout, PointLayoutBase} = require '../layout'
-{log, isPlainObject, min, max, createWithPostCreate, isNumber} = Foundation
+{log, isPlainObject, min, max, createWithPostCreate, isNumber, merge} = Foundation
 {color, Color, point, Point, rect, Rectangle, matrix, Matrix, point0, point1} = Atomic
 {GradientFillStyle} = Canvas
 # can be a gradient fill or a solid-color fill
@@ -28,6 +28,11 @@
     shadow:
       default: null
       validate: (v) -> !v || isPlainObject v
+      preprocess: (v) ->
+        if (offset = v?.offset) && !(offset instanceof PointLayoutBase)
+          merge v, offset: new PointLayout offset
+        else
+          v

   _expandRectangleByShadow: _expandRectangleByShadow = (r, shadow) ->
     return r unless shadow
@@ -52,17 +57,24 @@

   _prepareDrawOptions: (drawOptions, compositeMode, opacity)->
     super
-    {_shadow, _colors} = @
+    {_shadow, _colors, _currentSize} = @
+    if offset = _shadow?.offset
+      {x, y} = offset.layout _currentSize
+      _shadow = merge _shadow,
+        offsetX: x - _currentSize.x / 2
+        offsetY: y - _currentSize.y / 2
+
     drawOptions.shadow = _shadow

     drawOptions.colors = null
+    drawOptions.gradientRadius = null
     drawOptions.gradientRadius1 = null
     drawOptions.gradientRadius2 = null
     drawOptions.from = null
     drawOptions.to = null

     if _colors
-      {_from, _to, _gradientRadius, _currentSize} = @
+      {_from, _to, _gradientRadius} = @
       _from ||= defaultFrom

       drawOptions.colors = _colors


..and fix again bug caused by changing id

--- lms/static/coffee/src/discussion.coffee
+++ lms/static/coffee/src/discussion.coffee
@@ -300,7 +300,7 @@

     $local(".discussion-search-form").submit (event) ->
       event.preventDefault()
-      text = $local(".searchInput").val()
+      text = $local(".search-input").val()
       isSearchWithinBoard = $local(".discussion-search-within-board").is(":checked")
       handleSearch(text, isSearchWithinBoard)


Don't select first item of `SelectList` twice

--- src/app/select-list.coffee
+++ src/app/select-list.coffee
@@ -43,7 +43,6 @@

   setArray: (@array) ->
     @populateList()
-    @selectItem(@list.find('li:first'))
     @setLoading()

   setError: (message) ->