added isBoolean option for requests that return true or false

Examples: isStarred, isMember. Required switching _request to take
another argument so I collapsed the args into an options object

--- github.coffee
+++ github.coffee
@@ -30,18 +30,19 @@
     # These are updated whenever a request is made
     _listeners = []

-    constructor: (options={}) ->
+    constructor: (clientOptions={}) ->
       # Provide an option to override the default URL
-      options.rootURL = options.rootURL or 'https://api.github.com'
-
-      _request = (method, path, data, raw, isBase64) ->
+      clientOptions.rootURL = clientOptions.rootURL or 'https://api.github.com'
+
+
+      _request = (method, path, data, options={raw:false, isBase64:false, isBoolean:false}) ->
         getURL = ->
-          url = options.rootURL + path
+          url = clientOptions.rootURL + path
           url + ((if (/\?/).test(url) then '&' else '?')) + (new Date()).getTime()

         # Support binary data by overriding the response mimeType
         mimeType = undefined
-        mimeType = 'text/plain; charset=x-user-defined' if isBase64
+        mimeType = 'text/plain; charset=x-user-defined' if options.isBase64

         headers = {
           'Accept': 'application/vnd.github.raw'
@@ -52,15 +53,14 @@
         # See http://developer.github.com/v3/#user-agent-required
         headers['User-Agent'] = userAgent if userAgent

-        if (options.auth is 'oauth' and options.token) or (options.auth is 'basic' and options.username and options.password)
-          if options.auth is 'oauth'
-            auth = "token #{options.token}"
+        if (clientOptions.auth is 'oauth' and clientOptions.token) or (clientOptions.auth is 'basic' and clientOptions.username and clientOptions.password)
+          if clientOptions.auth is 'oauth'
+            auth = "token #{clientOptions.token}"
           else
-            auth = 'Basic ' + base64encode("#{options.username}:#{options.password}")
+            auth = 'Basic ' + base64encode("#{clientOptions.username}:#{clientOptions.password}")
           headers['Authorization'] = auth

-
-        xhr = jQuery.ajax {
+        ajaxConfig =
           url: getURL()
           type: method
           contentType: 'application/json'
@@ -68,47 +68,69 @@
           headers: headers

           processData: false # Don't convert to QueryString
-          data: !raw and data and JSON.stringify(data) or data
-          dataType: 'json' unless raw
-
-        }
-
-
-        xhr
-        .always =>
+          data: !options.raw and data and JSON.stringify(data) or data
+          dataType: 'json' unless options.raw
+
+        # If the request is a boolean yes/no question GitHub will indicate
+        # via the HTTP Status of 204 (No Content) or 404 instead of a 200.
+        # Also, jQuery will never call `xhr.resolve` so we need to use a
+        # different promise later on.
+        if options.isBoolean
+          booleanPromise = new jQuery.Deferred()
+          ajaxConfig.statusCode =
+            # a Boolean 'yes'
+            204: () => booleanPromise.resolve(true)
+            # a Boolean 'no'
+            404: () => booleanPromise.resolve(false)
+
+        xhr = jQuery.ajax(ajaxConfig)
+
+        xhr.always =>
           # Fire listeners when the request completes or fails
           rateLimit = parseFloat(xhr.getResponseHeader 'X-RateLimit-Limit')
           rateLimitRemaining = parseFloat(xhr.getResponseHeader 'X-RateLimit-Remaining')

           for listener in _listeners
-            listener(rateLimitRemaining, rateLimit, method, path, data, raw, isBase64)
-
-        # Return the result and Base64 encode it if `isBase64` flag is set.
-        .then (data, textStatus, jqXHR) ->
-          ret = new jQuery.Deferred()
-          # Convert the response to a Base64 encoded string
-          if 'GET' == method and isBase64
-            # Convert raw data to binary chopping off the higher-order bytes in each char.
-            # Useful for Base64 encoding.
-            converted = ''
-            for i in [0..data.length]
-              converted += String.fromCharCode(data.charCodeAt(i) & 0xff)
-            converted
-
-            ret.resolve(converted, textStatus, jqXHR)
-          else
-            ret.resolve(data, textStatus, jqXHR)
-
-        # Parse the error if one occurs
-        .then null, (xhr, msg, desc) ->
-          if xhr.getResponseHeader('Content-Type') != 'application/json; charset=utf-8'
-            return {error: xhr.responseText, status: xhr.status, _xhr: xhr}
-
-          json = JSON.parse xhr.responseText
-          return {error: json, status: xhr.status, _xhr: xhr}
+            listener(rateLimitRemaining, rateLimit, method, path, data, options)
+
+        promise = if options.isBoolean
+          # If the request fails for something other than 404
+          # then reject the promise.
+          # 404 means 'false'
+          xhr.fail (err) -> booleanPromise.reject(err) if err.status != 404
+
+          # Set the promise returned to be the boolean promise
+          # instead of `xhr` (see above on the `isBoolean` option)
+          booleanPromise
+
+        else
+
+          # Return the result and Base64 encode it if `options.isBase64` flag is set.
+          xhr.then (data, textStatus, jqXHR) ->
+            ret = new jQuery.Deferred()
+            # Convert the response to a Base64 encoded string
+            if 'GET' == method and options.isBase64
+              # Convert raw data to binary chopping off the higher-order bytes in each char.
+              # Useful for Base64 encoding.
+              converted = ''
+              for i in [0..data.length]
+                converted += String.fromCharCode(data.charCodeAt(i) & 0xff)
+              converted
+
+              ret.resolve(converted, textStatus, jqXHR)
+            else
+              ret.resolve(data, textStatus, jqXHR)
+
+          # Parse the error if one occurs
+          .then null, (xhr, msg, desc) ->
+            if xhr.getResponseHeader('Content-Type') != 'application/json; charset=utf-8'
+              return {error: xhr.responseText, status: xhr.status, _xhr: xhr}
+
+            json = JSON.parse xhr.responseText
+            return {error: json, status: xhr.status, _xhr: xhr}

         # Return the promise
-        .promise()
+        return promise.promise()

       # Add a listener that fires when the `rateLimitRemaining` changes as a result of
       # communicating with github.
@@ -119,7 +141,7 @@
       # -------
       @getZen = () ->
         # Send `data` to `null` and the `raw` flag to `true`
-        _request 'GET', '/zen', null, true
+        _request 'GET', '/zen', null, {raw:true}

       # Get all users
       # -------
@@ -219,7 +241,7 @@
           # Check if this user is following another user
           # -------
           @isFollowing = (user) ->
-            _request 'GET', "#{_rootPath}/following/#{user}", null
+            _request 'GET', "#{_rootPath}/following/#{user}", null, {isBoolean:true}

           # List public keys for a user
           # -------
@@ -334,7 +356,7 @@
             _request 'GET', "/teams/#{@id}/members"

           @isMember = (user) ->
-            _request 'GET', "/teams/#{@id}/members/#{user}"
+            _request 'GET', "/teams/#{@id}/members/#{user}", null, {isBoolean:true}

           @addMember = (user) ->
             _request 'PUT', "/teams/#{@id}/members/#{user}"
@@ -378,7 +400,7 @@
             _request 'GET', "/orgs/#{@name}/members", null

           @isMember = (user) ->
-            _request 'GET', "/orgs/#{@name}/members/#{user}", null
+            _request 'GET', "/orgs/#{@name}/members/#{user}", null, {isBoolean:true}

           @removeMember = (user) ->
             _request 'DELETE', "/orgs/#{@name}/members/#{user}", null
@@ -463,7 +485,7 @@
           # Retrieve the contents of a blob
           # -------
           @getBlob = (sha, isBase64) ->
-            _request 'GET', "#{_repoPath}/git/blobs/#{sha}", null, 'raw', isBase64
+            _request 'GET', "#{_repoPath}/git/blobs/#{sha}", null, {raw:true, isBase64:isBase64}

           # For a given file path, get the corresponding sha (blob for files, tree for dirs)
@@ -847,10 +869,11 @@
           # all organization owners are included in the list of collaborators.
           # Otherwise, only users with access to the repository are returned in the collaborators list.
           @getCollaborators = () ->
-            _request 'GET', "#{@repoPath}/collaborators"
-
-          @isCollaborator = (username) ->
-            _request 'GET', "#{@repoPath}/collaborators/#{username}"
+            _request 'GET', "#{@repoPath}/collaborators", null
+
+          @isCollaborator = (username=null) ->
+            throw 'BUG: username is required' if not username
+            _request 'GET', "#{@repoPath}/collaborators/#{username}", null, {isBoolean:true}

       # Gist API
@@ -929,7 +952,7 @@
           # Check if a gist is starred
           # -------
           @isStarred = () ->
-            _request 'GET', "#{@gistPath}"
+            _request 'GET', "#{@gistPath}", null, {isBoolean:true}

       # Top Level API


No longer need to create these files for restapi

--- src/bundle.coffee
+++ src/bundle.coffee
@@ -17,9 +17,6 @@
       tmp.dir (err, tmpdir) ->
         if err then return callback err

-        # TODO: remove once the rest-api stops asserting that these exist
-        ['gadget.js', 'gadget.css'].map touchLegacyFile.bind @, tmpdir
-
         console.log chalk.yellow('Reading source directory:')
         reader = fstream.Reader({ path: dir, type: 'Directory', filter })
         reader.on 'error', callback
@@ -28,11 +25,6 @@
         reader.pipe(fstream.Writer({ path: tmpdir, type: 'Directory' }))
           .on('error', callback)
           .on('end', zipFilesInFolder.bind(this, tmpdir, callback))
-
-touchLegacyFile = (dir, fileName) ->
-  filePath = path.join dir, fileName
-  unless fs.existsSync filePath
-    fs.writeFileSync filePath, '/* Nothing to see here */'

 zipFilesInFolder = (tmpdir, callback) ->
   console.log chalk.yellow('Creating bundle.zip')


Undo prev. commit

Undo prev. commit

--- src/coffee.coffee
+++ src/coffee.coffee
@@ -444,7 +444,7 @@
         when 'Call'
           @addBlock node, depth, precedence, COLORS.COMMAND, wrappingParen

-          if node.variable? and (node.do or node.variable.base?.nodeType() isnt 'Literal')
+          if node.variable? and node.variable.base?.nodeType() isnt 'Literal'
             @addSocketAndMark node.variable, depth + 1, 0, indentDepth

           unless node.do


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color2.coffee
+++ src/color2.coffee
@@ -15,10 +15,12 @@

     switch hexString.length
       when 3, 4
+        alpha = (parseInt(hexString.substr(3, 1), 16) * 0x11) / 255.0 if hexString.length == 4
+
         rgb = for i in [0..2]
           parseInt(hexString.substr(i, 1), 16) * 0x11

-        return rgb.concat(if hexString.substr(3, 1).length then (parseInt(hexString.substr(3, 1), 16) * 0x11) / 255.0 else 1.0)
+        return rgb.concat(alpha)

       when 6, 8
         rgb = for i in [0..2]


Notification: Fix "Notification message owner is wrong" bug [Social:73413852]

--- client/Main/CommonViews/notificationlistitemview.coffee
+++ client/Main/CommonViews/notificationlistitemview.coffee
@@ -115,7 +115,7 @@
             KD.remote.api.JAccount.one _id: message.accountOldId, (err, origin)=>
               return reject err  if err or not origin

-              adjective = if message.accounOldId is KD.whoami()?.getId() then "your"
+              adjective = if message.accountOldId is KD.whoami()?.getId() then "your"
               else if @actors.length == 1 and @actors[0].getId() is origin.getId() then "their own"
               else
                 originatorName = KD.utils.getFullnameFromAccount origin


Activity: minor cleanup

Activity: minor cleanup

--- client/Social/Activity/views/privatemessage/pane.coffee
+++ client/Social/Activity/views/privatemessage/pane.coffee
@@ -28,10 +28,11 @@
         @input.input.setPlaceholder ''
         @scrollDown()

-
-    @listController.getListView().on 'ItemWasAdded', @bound 'messageAdded'
-    @listController.getListView().on 'ItemWasRemoved', @bound 'messageRemoved'
-    @listController.getListView().on 'EditMessageReset', @input.bound 'focus'
+    list = @listController.getListView()
+
+    list.on 'ItemWasAdded',     @bound 'messageAdded'
+    list.on 'ItemWasRemoved',   @bound 'messageRemoved'
+    list.on 'EditMessageReset', @input.bound 'focus'

     KD.singleton('windowController').on 'ScrollHappened', @bound 'handleScroll'

@@ -54,7 +55,7 @@

   replaceFakeItemView: (message) ->
     index = @listController.getListView().getItemIndex @fakeMessageMap[message.clientRequestId]
-    item = @putMessage message, index
+    item  = @putMessage message, index
     @removeFakeMessage message.clientRequestId
     @scrollDown item

@@ -114,8 +115,13 @@

     super channel

-
-
+    channel.on 'AddedToChannel', @bound 'addParticipant'
+
+
+
+
+
+  # this is the realtime event handler for messages
   addMessage: (message) ->

     return  if message.account._id is KD.whoami()._id


KodingClient: Added “changedToLoggedIn` event

--- client/app/MainApp/maincontroller/maincontroller.coffee
+++ client/app/MainApp/maincontroller/maincontroller.coffee
@@ -61,13 +61,8 @@

       console.timeEnd "Koding.com loaded"

-  accountChanged:(account, firstLoad = no)->
-    @userAccount             = account
-    connectedState.connected = yes
-
-    @on "pageLoaded.as.loggedIn", (account)=> # ignore othter parameters
-      KD.utils.setPreferredDomain account if account
-      if registerToKodingClient = $.cookie "register-to-koding-client"
+  registerKodingClient: ->
+    if registerToKodingClient = $.cookie "register-to-koding-client"
         clear = ->
           $.cookie "register-to-koding-client", erase:yes
           window.location.pathname = "/"
@@ -129,12 +124,23 @@
               hostname:result.hostID
           }, (err, res)=>
             fn = => k.tell "info", handleInfo
-            return showErrorModal err, fn if err
-            clear()
-            result.cb true
-            KD.utils.wait 500, clear
+            return showErrorModal err.message, fn if err
+            showSuccessfulModal res, =>
+              result.cb true
+              KD.utils.wait 500, clear

         k.tell "info", handleInfo
+
+  accountChanged:(account, firstLoad = no)->
+    @userAccount             = account
+    connectedState.connected = yes
+
+    @on "pageLoaded.as.loggedIn", (account)=> # ignore othter parameters
+      KD.utils.setPreferredDomain account if account
+      @emit "changedToLoggedIn"
+
+    @once "accountChanged.to.loggedIn", (account)=> # ignore othter parameters
+      @emit "changedToLoggedIn"

     account.fetchMyPermissionsAndRoles (err, permissions, roles)=>
       return warn err  if err
@@ -179,6 +185,9 @@
   attachListeners:->
     # @on 'pageLoaded.as.(loggedIn|loggedOut)', (account)=>
     #   log "pageLoaded", @isUserLoggedIn()
+
+    @once 'changedToLoggedIn', (account)=>
+      @registerKodingClient()

     # TODO: this is a kludge we needed.  sorry for this.  Move it someplace better C.T.
     wc = @getSingleton 'windowController'


removing jQuery class toggle

removing jQuery class toggle

--- client/controllers/incidentReports/incidentTable.coffee
+++ client/controllers/incidentReports/incidentTable.coffee
@@ -20,7 +20,6 @@
   'click table.incident-table tr': (event, instance) ->
     currentId = $(event.target).parent('tr').children('.count').data("incident-id")
     instance.data.incidents.update({_id: currentId}, {$set: {selected: true}})
-    $(event.target).parent('tr').toggleClass('active')

   'click .reject': (event, instance) ->
     changeIncidentStatus(false, instance)


remove Post model

remove Post model

--- src/models/post.coffee
+++ src/models/post.coffee
@@ -1,10 +0,0 @@
-mongoose = require 'mongoose'
-
-# Post model
-Post = new mongoose.Schema(
-  title: String
-  body: String
-  url: String
-)
-
-module.exports = mongoose.model 'Post', Post


:fire: Remove deprecations from DisplayBuffer

--- src/display-buffer.coffee
+++ src/display-buffer.coffee
@@ -2,7 +2,6 @@
 Serializable = require 'serializable'
 {CompositeDisposable, Emitter} = require 'event-kit'
 {Point, Range} = require 'text-buffer'
-Grim = require 'grim'
 TokenizedBuffer = require './tokenized-buffer'
 RowMap = require './row-map'
 Fold = require './fold'
@@ -157,11 +156,9 @@
     @emitter.on 'did-update-markers', callback

   emitDidChange: (eventProperties, refreshMarkers=true) ->
-    @emit 'changed', eventProperties if Grim.includeDeprecatedAPIs
     @emitter.emit 'did-change', eventProperties
     if refreshMarkers
       @refreshMarkerScreenPositions()
-    @emit 'markers-updated' if Grim.includeDeprecatedAPIs
     @emitter.emit 'did-update-markers'

   updateWrappedScreenLines: ->
@@ -319,7 +316,6 @@

   characterWidthsChanged: ->
     @computeScrollWidth()
-    @emit 'character-widths-changed', @scopedCharacterWidthsChangeCount if Grim.includeDeprecatedAPIs
     @emitter.emit 'did-change-character-widths', @scopedCharacterWidthsChangeCount

   clearScopedCharWidths: ->
@@ -437,7 +433,6 @@
       @softWrapped = softWrapped
       @updateWrappedScreenLines()
       softWrapped = @isSoftWrapped()
-      @emit 'soft-wrap-changed', softWrapped if Grim.includeDeprecatedAPIs
       @emitter.emit 'did-change-soft-wrapped', softWrapped
       softWrapped
     else
@@ -999,7 +994,6 @@
     @decorationsByMarkerId[marker.id].push(decoration)
     @overlayDecorationsById[decoration.id] = decoration if decoration.isType('overlay')
     @decorationsById[decoration.id] = decoration
-    @emit 'decoration-added', decoration if Grim.includeDeprecatedAPIs
     @emitter.emit 'did-add-decoration', decoration
     decoration

@@ -1011,7 +1005,6 @@
     if index > -1
       decorations.splice(index, 1)
       delete @decorationsById[decoration.id]
-      @emit 'decoration-removed', decoration if Grim.includeDeprecatedAPIs
       @emitter.emit 'did-remove-decoration', decoration
       delete @decorationsByMarkerId[marker.id] if decorations.length is 0
       delete @overlayDecorationsById[decoration.id]
@@ -1310,7 +1303,6 @@
     if marker = @getMarker(textBufferMarker.id)
       # The marker might have been removed in some other handler called before
       # this one. Only emit when the marker still exists.
-      @emit 'marker-created', marker if Grim.includeDeprecatedAPIs
       @emitter.emit 'did-create-marker', marker

   decorateFold: (fold) ->
@@ -1339,57 +1331,15 @@
     atom.assert screenLinesCount is bufferLinesCount, "Display buffer line count out of sync with buffer", (error) ->
       error.metadata = {screenLinesCount, tokenizedLinesCount, bufferLinesCount}

-if Grim.includeDeprecatedAPIs
-  DisplayBuffer.properties
-    softWrapped: null
-    editorWidthInChars: null
-    lineHeightInPixels: null
-    defaultCharWidth: null
-    height: null
-    width: null
-    scrollTop: 0
-    scrollLeft: 0
-    scrollWidth: 0
-    verticalScrollbarWidth: 15
-    horizontalScrollbarHeight: 15
-
-  EmitterMixin = require('emissary').Emitter
-
-  DisplayBuffer::on = (eventName) ->
-    switch eventName
-      when 'changed'
-        Grim.deprecate("Use DisplayBuffer::onDidChange instead")
-      when 'grammar-changed'
-        Grim.deprecate("Use DisplayBuffer::onDidChangeGrammar instead")
-      when 'soft-wrap-changed'
-        Grim.deprecate("Use DisplayBuffer::onDidChangeSoftWrap instead")
-      when 'character-widths-changed'
-        Grim.deprecate("Use DisplayBuffer::onDidChangeCharacterWidths instead")
-      when 'decoration-added'
-        Grim.deprecate("Use DisplayBuffer::onDidAddDecoration instead")
-      when 'decoration-removed'
-        Grim.deprecate("Use DisplayBuffer::onDidRemoveDecoration instead")
-      when 'decoration-changed'
-        Grim.deprecate("Use decoration.getMarker().onDidChange() instead")
-      when 'decoration-updated'
-        Grim.deprecate("Use Decoration::onDidChangeProperties instead")
-      when 'marker-created'
-        Grim.deprecate("Use Decoration::onDidCreateMarker instead")
-      when 'markers-updated'
-        Grim.deprecate("Use Decoration::onDidUpdateMarkers instead")
-      else
-        Grim.deprecate("DisplayBuffer::on is deprecated. Use event subscription methods instead.")
-
-    EmitterMixin::on.apply(this, arguments)
-else
-  DisplayBuffer::softWrapped = null
-  DisplayBuffer::editorWidthInChars = null
-  DisplayBuffer::lineHeightInPixels = null
-  DisplayBuffer::defaultCharWidth = null
-  DisplayBuffer::height = null
-  DisplayBuffer::width = null
-  DisplayBuffer::scrollTop = 0
-  DisplayBuffer::scrollLeft = 0
-  DisplayBuffer::scrollWidth = 0
-  DisplayBuffer::verticalScrollbarWidth = 15
-  DisplayBuffer::horizontalScrollbarHeight = 15
+DisplayBuffer.properties
+  softWrapped: null
+  editorWidthInChars: null
+  lineHeightInPixels: null
+  defaultCharWidth: null
+  height: null
+  width: null
+  scrollTop: 0
+  scrollLeft: 0
+  scrollWidth: 0
+  verticalScrollbarWidth: 15
+  horizontalScrollbarHeight: 15


Always show current user's notification settings

--- core/app/backbone/controllers/profile_controller.coffee
+++ core/app/backbone/controllers/profile_controller.coffee
@@ -11,13 +11,11 @@
     FactlinkApp.mainRegion.show new ReactView component:
       ReactProfile model: user

+
   showNotificationSettings: (username) ->
-    user = new User(username: username)
-    user.fetch()
-
     FactlinkApp.mainRegion.show new ReactView
       component: ReactNotificationSettings
-        model: user
+        model: session.user()

   showChangePassword: ->
     FactlinkApp.mainRegion.show new ReactView


fix merge mistake in prometheus PR

fix merge mistake in prometheus PR

--- src/smc-hub/hub.coffee
+++ src/smc-hub/hub.coffee
@@ -496,7 +496,6 @@
 # Start everything running
 #############################################
 BASE_URL = ''
-metricsRecorder = undefined
 metric_blocked  = undefined

 exports.start_server = start_server = (cb) ->
@@ -579,7 +578,7 @@
                 dev            : program.dev
                 compute_server : compute_server
                 database       : database
-                metricsRecorder: metricsRecorder
+                metricsRecorder: MetricsRecorder
             {http_server, express_router} = x
             winston.debug("starting express webserver listening on #{program.host}:#{program.port}")
             http_server.listen(program.port, program.host, cb)
@@ -604,7 +603,7 @@
         (cb) ->
             if not program.port
                 cb(); return
-            metricsRecorder.setup_monitoring()
+            MetricsRecorder.setup_monitoring()
             cb()
     ], (err) =>
         if err


Changed function binding for this scope

Changed function binding for this scope

--- src/plugins/cleanhtml.coffee
+++ src/plugins/cleanhtml.coffee
@@ -47,7 +47,7 @@
       editor = this.element

       # bind paste handler on first call
-      editor.bind 'paste', this, (event) ->
+      editor.bind 'paste', this, (event) =>

         # TODO: find out why this check always fails when placed directly
         # after jQuery.htmlClean check
@@ -65,7 +65,7 @@
         lastContent = editor.html()
         editor.html ''

-        setTimeout ->
+        setTimeout =>

           pasted = editor.html()
           cleanPasted = jQuery.htmlClean pasted, @options


global toggle for backbutton

global toggle for backbutton

--- vnc-example01.framer/modules/ViewNavigationController.coffee
+++ vnc-example01.framer/modules/ViewNavigationController.coffee
@@ -38,6 +38,7 @@
 		@history = []
 		@animationOptions = @options.animationOptions or ANIMATION_OPTIONS
 		@initialViewName  = @options.initialViewName  or INITIAL_VIEW_NAME
+		@backButton       = @options.backButton ?= true
 		@backButtonFrame  = @options.backButtonFrame  or BACK_BUTTON_FRAME
 		@debugMode        = @options.debugMode        or DEBUG_MODE

@@ -80,8 +81,9 @@

 		unless view.superLayer is @ or viaInternalChangeEvent
 			view.superLayer = @
-
-		@_applyBackButton view unless view.name is @initialViewName
+
+		if @backButton is true
+			@_applyBackButton view unless view.name is @initialViewName

 		@views.push view


Implemented cancel functionality

Implemented cancel functionality

--- core/app/backbone/views/evidence/ndp_evidence_collection_view.coffee
+++ core/app/backbone/views/evidence/ndp_evidence_collection_view.coffee
@@ -62,8 +62,8 @@

-

-

+

+

@@ -79,12 +79,17 @@
   events:
     'click .js-supporting-button': 'showAddSupporting'
     'click .js-weakening-button': 'showAddWeakening'
+    'click .js-cancel': 'cancel'

   onRender: ->
-    @ui.box.hide()
+    @cancel()

   showAddSupporting: -> @_showAdd true
   showAddWeakening:  -> @_showAdd false
+
+  cancel: ->
+    @ui.box.hide()
+    @ui.buttons.show()

   _showAdd: (supporting) ->
     @ui.buttons.hide()


Fix bubble padding.

Fix bubble padding.

fix #3823

--- app/assets/javascripts/branch-graph.js.coffee
+++ app/assets/javascripts/branch-graph.js.coffee
@@ -5,7 +5,7 @@
     @mspace = 0
     @parents = {}
     @colors = ["#000"]
-    @offsetX = 120
+    @offsetX = 150
     @offsetY = 20
     @unitTime = 30
     @unitSpace = 10


Leaflet busca imágenes sin digest. Workaround con links simbólicos

--- app/assets/javascripts/comunes/mapa.js.coffee
+++ app/assets/javascripts/comunes/mapa.js.coffee
@@ -2,6 +2,7 @@
   if $('#mapa').length

     # Definir el path de los assets para las imágenes
+    # FIXME Leaflet busca imágenes sin digest
     L.Icon.Default.imagePath = '/estaticos/leaflet'

     # Genera el markup para cada punto del geoJson


Update names.coffee

Update names.coffee

--- app/lib/world/names.coffee
+++ app/lib/world/names.coffee
@@ -419,7 +419,7 @@
   'Sorcerer': [
     'Beazer'
     'Gandalf'
-  'Pender'
+    'Pender'
   ]
   'Samurai': [
     'Hattori'


:bug:Classroom last played string null level check

--- app/views/courses/ClassroomView.coffee
+++ app/views/courses/ClassroomView.coffee
@@ -157,7 +157,10 @@
     course = session.collection.course
     levelOriginal = session.get('level').original
     level = @levels.findWhere({original: levelOriginal})
-    return "#{course.get('name')}, #{level.get('name')}"
+    lastPlayed = ""
+    lastPlayed += course.get('name') if course
+    lastPlayed += ", #{level.get('name')}" if level
+    lastPlayed

   userPlaytimeString: (user) ->
     return '' unless user.sessions?


Properly reset distro select on location change

--- app/assets/javascripts/servers/form.js.coffee
+++ app/assets/javascripts/servers/form.js.coffee
@@ -47,9 +47,9 @@
     $(this).tab('show')
     $(this).parent('li').addClass('active')

-    $provisionerRole.select2('val', null)
-    $distro.select2('val', null)
-    $template.select2('val', null)
+    $provisionerRole.val([])
+    $distro.val([])
+    $template.val([])

   $costs            = $('span.total-cost')
   $boxes            = $('ul.package-boxes').find('> li')
@@ -349,22 +349,6 @@
     .select2 'enable', enable

   # Populate the "Select a distro" dropdown
-  $distro.select2
-    placeholder: 'Select a distro'
-    data:
-      results:  _.sortBy $distroSelect, 'distro'
-    formatSelection: distroFormat
-    formatResult: distroFormat
-
-  # Once a distro has been chosen, populate the template dropdown with that distro's available
-  # templates.
-  .on 'change', (e) ->
-    data = e.added
-    distro = data.id
-    populateTemplateSelect(distro)
-    # Ensure there is no template selected yet
-    $templateId.val(-1)
-
   populateDistros = ->
     $distro.select2
       placeholder: 'Select a distro'
@@ -372,6 +356,18 @@
         results:  _.sortBy $distroSelect, 'distro'
       formatSelection: distroFormat
       formatResult: distroFormat
+    # Once a distro has been chosen, populate the template dropdown with that distro's available templates.
+    .on 'change', (e) ->
+      data = e.added
+      distro = data.id
+      populateTemplateSelect(distro)
+      # Ensure there is no template selected yet
+      $template.val([])
+
+    $template.val([])
+    $distro.val([])
+
+  populateDistros()

   # Set template to Docker provisioner template
   loadProvisionerRole = (provisioner_templates) ->
@@ -383,7 +379,7 @@
         $template.val template.id
       .select2 'enable', true
     else
-      $provisionerRole.select2 'val', null
+      $provisionerRole.val([])
       $provisionerRole.select2 'enable', false

   # Initialise the templates dropdown
@@ -484,9 +480,9 @@
       $(activeTab).show()

       if !server
-        $provisionerRole.select2('val', null)
-        $distro.select2('val', null)
-        $template.select2('val', null)
+        $provisionerRole.val([])
+        $distro.val([])
+        $template.val([])

     if $('#server_wizard_provisioner_role').val() != ''
       $('#jg-tabs li:nth-child(2) a').click()


Fix selection behavior at beginning or end of line

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -942,14 +942,16 @@
       for selection in selections
         charToLeftOfSelection = new Range(selection.start.translate(translationDelta), selection.start)

-        unless charToLeftOfSelection.start.column < 0
+        if charToLeftOfSelection.start.column < 0
+          translatedRanges.push(selection)
+        else
           text = @buffer.getTextInRange(charToLeftOfSelection)

           @buffer.insert(selection.end, text)
           @buffer.delete(charToLeftOfSelection)
           translatedRanges.push(selection.translate(translationDelta))

-      @setSelectedBufferRanges(translatedRanges) if translatedRanges.length > 0
+      @setSelectedBufferRanges(translatedRanges)

   moveSelectionRight: ->
     selections = @getSelectedBufferRanges()
@@ -961,14 +963,16 @@
       for selection in selections
         charToRightOfSelection = new Range(selection.end, selection.end.translate(translationDelta))

-        unless charToRightOfSelection.end.column > @buffer.lineLengthForRow(charToRightOfSelection.end.row)
+        if charToRightOfSelection.end.column > @buffer.lineLengthForRow(charToRightOfSelection.end.row)
+          translatedRanges.push(selection)
+        else
           text = @buffer.getTextInRange(charToRightOfSelection)

           @buffer.delete(charToRightOfSelection)
           @buffer.insert(selection.start, text)
           translatedRanges.push(selection.translate(translationDelta))

-      @setSelectedBufferRanges(translatedRanges) if translatedRanges.length > 0
+      @setSelectedBufferRanges(translatedRanges)

   # Duplicate the most recent cursor's current line.
   duplicateLines: ->


:bug: Fix top of a wrapped line not visible when moving it up

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -949,7 +949,7 @@

       @setSelectedBufferRanges(newSelectionRanges, {autoscroll: false, preserveFolds: true})
       @autoIndentSelectedRows() if @shouldAutoIndent()
-      @scrollToBufferPosition([newSelectionRanges[0].start.row + 2, 0])
+      @scrollToBufferPosition([newSelectionRanges[0].start.row, 0])

   # Move lines intersecting the most recent selection or muiltiple selections down by one row in screen
   # coordinates.


em home modal's close logic

em home modal's close logic

--- app/assets/javascripts/em.js.coffee
+++ app/assets/javascripts/em.js.coffee
@@ -56,4 +56,6 @@
                     player.addEvent "finish", onFinish
             else
                 $('.modal-wrapper').hide()
+
+    $('a.icon-close').click -> $('.modal-wrapper').hide()
 )


improved interpolation efficiency..again

--- app/assets/javascripts/model.coffee
+++ app/assets/javascripts/model.coffee
@@ -169,10 +169,10 @@
 	#
 	get : (vertices, callback) ->

-		# console.profile("Model.Binary.get")
+		console.time("get")
 		_this  = @
 		_value = (x0, y0, z0) ->
-			_this.value(x0, y0, z0)
+			_this.getColor(x0, y0, z0)

 		colors = new Float32Array(vertices.length / 3 >> 0)

@@ -184,7 +184,7 @@

 			colors[i / 3] = interpolate(x, y, z, _value)

-		# console.profileEnd("Model.Binary.get")
+		console.timeEnd("get")
 		callback(null, colors) if callback

@@ -331,9 +331,9 @@

 		_bucket_width = @BUCKET_WIDTH

-		(x % _bucket_width >> 0) +
-		(y % _bucket_width >> 0) * _bucket_width +
-		(z % _bucket_width >> 0) * _bucket_width * _bucket_width
+		(x % _bucket_width) +
+		(y % _bucket_width) * _bucket_width +
+		(z % _bucket_width) * _bucket_width * _bucket_width

 	# Want to add data? Make sure the cuboid is big enough.
 	# This one is for passing real point coordinates.
@@ -399,6 +399,29 @@
 		@cubeOffset = cubeOffset
 		@cubeSize   = cubeSize

+	getColor : (x, y, z) ->
+
+		unless (_cube = @cube)
+			return 0
+
+		_bucket_width = @BUCKET_WIDTH
+		_offset       = @cubeOffset
+		_size         = @cubeSize
+
+		_bucketIndex =
+			(x / _bucket_width - _offset[0]) +
+			(y / _bucket_width - _offset[1]) * _size[0] +
+			(z / _bucket_width - _offset[2]) * _size[0] * _size[1] >> 0
+		_pointIndex =
+			(x % _bucket_width) +
+			(y % _bucket_width) * _bucket_width +
+			(z % _bucket_width) * _bucket_width * _bucket_width
+
+		if (_bucket = _cube[_bucketIndex])
+			_bucket[_pointIndex]
+		else
+			0
+
 	# Get or set a color value of a point.
 	# Keep in mind that 0 represents a undefined value.
 	# Real color values range from 1 to 2 -- with black being 0 and white 1.


Removing unused graph code from issues directives file

--- app/coffee/directives/issues.coffee
+++ app/coffee/directives/issues.coffee
@@ -29,155 +29,6 @@
                 icon.addClass("icon-chevron-down")

             event.preventDefault()
-            # scope.$emit("sortingOrder")
-            # scope.$apply()
-
-
-GmPendingIssueGraphDirective = -> (scope, elm, attrs) ->
-    redrawChart = () ->
-        element = angular.element elm
-        countIssues = (severities, issues) ->
-            counter = {}
-            for severity in severities
-                counter[severity.id] = 0
-
-            for issue in issues
-                if not counter[issue.severity]
-                    counter[issue.severity] = 1
-                else
-                    counter[issue.severity] += 1
-
-            result = []
-            for severity in severities
-                result.push(counter[severity.id])
-            result
-
-        width = element.width()
-        height = width
-
-        element.empty()
-        chart = $("<canvas />").attr("width", width).attr("height", height).attr("id", "issues-chart")
-        element.append(chart)
-
-        ctx = $("#issues-chart").get(0).getContext("2d")
-
-        options =
-            scaleFontFamily : "'ColabThi'",
-            scaleFontSize : 10
-
-        data = {
-            labels : (severity.name for severity in scope.constants.severitiesList)
-            datasets : [
-                #Number of created bugs
-                {
-                    fillColor : "rgba(120,120,120,0.2)",
-                    strokeColor : "rgba(120,120,120,0.2)",
-                    pointColor : "rgba(255,255,255,1)",
-                    pointStrokeColor : "#ccc",
-                    data : countIssues(scope.constants.severitiesList, scope.issues)
-                },
-                #Number of resolved bugs
-                {
-                    fillColor : "rgba(102,153,51,0.3)",
-                    strokeColor : "rgba(102,153,51,1)",
-                    pointColor : "rgba(255,255,255,1)",
-                    data : countIssues(scope.constants.severitiesList, _.filter(scope.issues, 'is_closed'))
-                }
-            ]
-        }
-
-        new Chart(ctx).Radar(data,options)
-
-    scope.$watch 'issues', (value) ->
-        redrawChart() if value
-
-
-GmYourIssuesGraphDirective = -> (scope, elm, attrs) ->
-    element = angular.element(elm)
-
-    redrawChart = () ->
-        countIssues = (severities, issues) ->
-            counter = {}
-            for severity in severities
-                counter[severity.id] = 0
-
-            for issue in issues
-                if not counter[issue.severity]
-                    counter[issue.severity] = 1
-                else
-                    counter[issue.severity] += 1
-
-            result = []
-            for severity in severities
-                result.push(counter[severity.id])
-            result
-
-        width = element.width()
-        height = width
-
-        element.empty()
-        chart = $("<canvas />").attr("width", width).attr("height", height).attr("id", "your-issues-chart")
-        element.prepend(chart)
-
-        ctx = $("#your-issues-chart").get(0).getContext("2d")
-
-        options =
-            scaleFontFamily : "'ColabThi'",
-            scaleFontSize : 10
-
-        colors = ["#ccc", "#669933", "blue", "orange", "#CC0000"]
-        counts = countIssues(scope.constants.severitiesList, scope.issues)
-        data = []
-
-        for x in [0..counts.length-1]
-            data.push value:counts[x], color:colors[x]
-
-        new Chart(ctx).Doughnut(data,options)
-
-    scope.$watch 'issues', (value) ->
-        redrawChart() if value
-
-
-GmIssuesCreationGraphDirective = -> (scope, elm, attrs) ->
-    redrawChart = () ->
-        element = angular.element(elm)
-
-        width = element.width()
-        height = 320
-
-        element.empty()
-        chart = $("<canvas />").attr("width", width).attr("height", height).attr("id", "issues-creation-chart")
-        element.prepend(chart)
-
-        ctx = $("#issues-creation-chart").get(0).getContext("2d")
-
-        options =
-            animation: false,
-            bezierCurve: false,
-            scaleFontFamily : "'ColabThi'",
-            scaleFontSize : 10
-
-        data = {
-            labels : ["Sprint1","Sprint2","Sprint3","Sprint4","Sprint5","Sprint6"],
-            datasets : [
-                {
-                    fillColor : "rgba(120,120,120,0.2)",
-                    strokeColor : "rgba(120,120,120,0.2)",
-                    pointColor : "rgba(255,255,255,1)",
-                    pointStrokeColor : "#ccc",
-                    data : [10, 15, 30, 40, 20, 10]
-                }
-            ]
-        }
-
-        new Chart(ctx).Line(data, options)
-
-    scope.$watch 'issues', (value) ->
-        redrawChart() if value
-

 module = angular.module('greenmine.directives.issues', [])
 module.directive('gmIssuesSort', ["$parse", GmIssuesSortDirective])
-module.directive("gmPendingIssueGraph", GmPendingIssueGraphDirective)
-module.directive("gmYourIssuesGraph", GmYourIssuesGraphDirective)
-module.directive("gmIssuesCreationGraph", GmIssuesCreationGraphDirective)


Fixed bug on resources service to class migration

--- app/coffee/services/resource.coffee
+++ app/coffee/services/resource.coffee
@@ -345,7 +345,7 @@
             return milestone

         # Third step: make milestone models
-        _makeModel = (milestone) ->
+        _makeModel = (milestone) =>
             return @model.make_model("milestone", milestone)

         return _getMilestone().then(_makeUserStoryModels).then(_makeModel)


only use location if it's on the map

only use location if it's on the map

--- src/ui/location.coffee
+++ src/ui/location.coffee
@@ -82,10 +82,9 @@
   positionOk = (evt) ->
     coords = evt.coords
     return if coords.accuracy > ACCURACY_LIMIT
-    location = {
-      pos: [coords.longitude, coords.latitude]
-      accuracy: coords.accuracy
-    }
+    pos = [coords.longitude, coords.latitude]
+    return unless app.inside(map.wgsProjection(pos), map.db.bbox)
+    location = {pos: pos, accuracy: coords.accuracy}

     center = false
     if locationMode == LOCATION_WAIT


Remove accidental project-status change

Remove accidental project-status change

--- app/pages/admin/project-status.cjsx
+++ app/pages/admin/project-status.cjsx
@@ -24,7 +24,6 @@
   'worldwide telescope'
   'hide previous marks'
   'column'
-  'workflow ranking'
 ]

 ProjectToggle = React.createClass


Donut: Fixed deselect()

Donut: Fixed deselect()

Cannot read property 'length' of undefined

--- app/scripts/directives/donut.coffee
+++ app/scripts/directives/donut.coffee
@@ -69,7 +69,7 @@
         )

         deselect = ->
-          for segment in donut.segments
+          for segment in donut.segments?
             segment.deselect()

           executeOnSelect(null, null)


Just generate 60 keyframes per second to simplify things

--- app/scripts/lib/bounce/index.coffee
+++ app/scripts/lib/bounce/index.coffee
@@ -7,7 +7,9 @@
   skew: require "./components/skew"

 class Bounce
+  @FPS: 60
   @counter: 1
+
   components: null
   duration: 0

@@ -94,33 +96,22 @@
     "@#{prefixes.animation}keyframes #{@name} { \n  #{keyframeList.join("\n  ")} \n}"

   getKeyframes: ->
-    # TODO: Clean this up, move logic to components
-    keys = [0]
-    for component in @components
-      startKey = (component.delay / @duration) * 100
-      endKey = ((component.delay + component.duration) / @duration) * 100
-
-      numKeyframes = component.easingObject.bounces * 8
-
-      keys.push(startKey + (i / numKeyframes) * (endKey - startKey)) for i in [0..numKeyframes]
-      keys.push(startKey - 0.01) unless startKey is 0
-
-    keys = keys.sort((a, b) -> a - b).map((i) -> i / 100)
-
-    @keys = @_unique(keys)
+    frames = Math.round((@duration / 1000) * Bounce.FPS)
+    @keys = []
+    @keys.push(i / frames) for i in [0..frames]

     keyframes = {}
-    for i in @keys
+    for key in @keys
       matrix = new Matrix4D().identity()

       for component in @components
-        currentTime = i * @duration
+        currentTime = key * @duration
         continue if (component.delay - currentTime) > 1e-8 # Account for rounding errors
-        ratio = (i - component.delay / @duration) / (component.duration / @duration)
+        ratio = (key - component.delay / @duration) / (component.duration / @duration)
         matrix.multiply \
           component.getEasedMatrix(ratio)

-      keyframes[i] = matrix.transpose().toFixed 5
+      keyframes[key] = matrix.transpose().toFixed 5

     keyframes


Update styling for first place ranks section

--- resources/assets/coffee/react/profile-page/top-ranks.coffee
+++ resources/assets/coffee/react/profile-page/top-ranks.coffee
@@ -48,7 +48,11 @@
       div null,
         h3
           className: 'page-extra__title page-extra__title--small'
-          "#{osu.trans('users.show.extra.top_ranks.first.title')} (#{@props.user.scores_first_count[0]})"
+          osu.trans('users.show.extra.top_ranks.first.title')
+          ' '
+          if @props.user.scores_first_count[0] > 0
+            span className: 'page-extra__title-count',
+              @props.user.scores_first_count[0].toLocaleString()
         if @props.scoresFirsts?.length
           div className: 'profile-extra-entries',
             el window._exported.PlayDetailList, scores: @props.scoresFirsts


fingerprints are grouped into lists with this change

fingerprints are shared when require.js pulls in other resources.
this change means changes to either ace.js or mode-latex.js will
result in different fingerprints for those files.

--- services/web/app/coffee/infrastructure/ExpressLocals.coffee
+++ services/web/app/coffee/infrastructure/ExpressLocals.coffee
@@ -7,6 +7,7 @@
 SystemMessageManager = require("../Features/SystemMessages/SystemMessageManager")
 AuthenticationController = require("../Features/Authentication/AuthenticationController")
 _ = require("underscore")
+async = require("async")
 Modules = require "./Modules"
 Url = require "url"

@@ -21,29 +22,46 @@
 		"/js/"

-logger.log "Generating file fingerprints..."
-for path in [
-	"#{jsPath}libs/require.js",
-	"#{jsPath}ide.js",
-	"#{jsPath}main.js",
-	"#{jsPath}libs.js",
-	"#{jsPath}ace/ace.js",
-	"#{jsPath}libs/pdfjs-1.3.91p1/pdf.js",
-	"#{jsPath}libs/pdfjs-1.3.91p1/pdf.worker.js",
-	"#{jsPath}libs/pdfjs-1.3.91p1/compatibility.js",
-	"/stylesheets/style.css"
-]
-	filePath = Path.join __dirname, "../../../", "public#{path}"
+getFileContent = (filePath)->
+	filePath = Path.join __dirname, "../../../", "public#{filePath}"
 	exists = fs.existsSync filePath
 	if exists
 		content = fs.readFileSync filePath
-		hash = crypto.createHash("md5").update(content).digest("hex")
-		logger.log "#{filePath}: #{hash}"
-		fingerprints[path] = hash
+		return content
 	else
 		logger.log filePath:filePath, "file does not exist for fingerprints"
+		return ""
+
+logger.log "Generating file fingerprints..."
+pathList = [
+	["#{jsPath}libs/require.js"]
+	["#{jsPath}ide.js"]
+	["#{jsPath}main.js"]
+	["#{jsPath}libs.js"]
+	["#{jsPath}ace/ace.js","#{jsPath}ace/mode-latex.js", "#{jsPath}ace/snippets/latex.js"]
+	["#{jsPath}libs/pdfjs-1.3.91p1/pdf.js"]
+	["#{jsPath}libs/pdfjs-1.3.91p1/pdf.worker.js"]
+	["#{jsPath}libs/pdfjs-1.3.91p1/compatibility.js"]
+	["/stylesheets/style.css"]
+]
+
+for paths in pathList
+	contentList = _.map(paths, getFileContent)
+	content = contentList.join("")
+	hash = crypto.createHash("md5").update(content).digest("hex")
+	_.each paths, (filePath)->
+		logger.log "#{filePath}: #{hash}"
+		fingerprints[filePath] = hash
+
+
+
+
+
+
+

 getFingerprint = (path) ->
+	console.log path, fingerprints[path], "!!!!!!!!!!!"
 	if fingerprints[path]?
 		return fingerprints[path]
 	else


Save 3 bytes

Save 3 bytes

--- src/objects/cmd.coffee
+++ src/objects/cmd.coffee
@@ -32,7 +32,7 @@

 dispatch = ->
   unless cmd = find argv[0]
-    find 'help'
+    find 'h'  # help command
     .i()
     upgrade.hint()
     exit 1


Test "errorOn404" option

Test "errorOn404" option

--- test/tests.coffee
+++ test/tests.coffee
@@ -16,6 +16,14 @@
       assert err.isHttpError
       done()

+  it 'obeys the errorOn404 option', (done) ->
+    request.get
+      url: "#{ testServerUrl }/404"
+      errorOn404: false
+      (err, res) ->
+        assert.isUndefined err
+        assert res.isHttpError
+        done()

   it 'sends headers', (done) ->
     req =


Update test for linux.

Update test for linux.

--- test/vigil.coffee
+++ test/vigil.coffee
@@ -1,7 +1,9 @@
 fs      = require 'fs'
-vigil   = require '../lib'
+os      = require('os')
 request = require 'request'
 should  = (require 'chai').should()
+
+vigil   = require '../lib'

 describe 'vigil', ->
@@ -22,8 +24,12 @@
     it 'should detect filechanges', (done) ->
       found = 0
       vigil.watch './test/assets', (filename, stats, isModule) ->
-        console.log filename
-        done() if ++found == 2
+        # each platform behaves slightly differently :(
+        if os.platform() == 'darwin'
+          done() if ++found == 2
+        else
+          done()
+
       require '../test/assets/test-module-2'
       fs.writeFileSync './test/assets/test-module-2', ''
       fs.writeFileSync './test/assets/2/3', ''


handle locations when disabled

handle locations when disabled

--- packages/rocketchat-mapview/client/mapview.coffee
+++ packages/rocketchat-mapview/client/mapview.coffee
@@ -10,15 +10,17 @@
 		mv_enabled = RocketChat.settings.get 'MapView_Enabled'
 		mv_googlekey = RocketChat.settings.get 'MapView_GMapsAPIKey'

-		if message.location and mv_enabled
+		if message.location

 			# GeoJSON is reversed - ie. [lng, lat]
 			longitude = message.location.coordinates[0]
 			latitude = message.location.coordinates[1]

 			# confirm we have an api key set, and generate the html required for the mapview
-			if mv_googlekey?.length
+			if mv_enabled and mv_googlekey?.length
 				message.html  = ''
+			else
+				message.html  = 'Shared Location'

 		return message


Provide some defaults.

Provide some defaults.

Otherwise we’ll get:

TypeError: Cannot read property 'pathToOpen' of undefined

when called from ‘application:new-file’.

--- src/browser/atom-application.coffee
+++ src/browser/atom-application.coffee
@@ -370,7 +370,7 @@
   #   :safeMode - Boolean to control the opened window's safe mode.
   #   :profileStartup - Boolean to control creating a profile of the startup time.
   #   :window - {AtomWindow} to open file paths in.
-  openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window}) ->
+  openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window} = {}) ->
     @openPaths({pathsToOpen: [pathToOpen], pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window})

   # Public: Opens multiple paths, in existing windows if possible.


update cmds

update cmds

--- src/clients/serialportclient.coffee
+++ src/clients/serialportclient.coffee
@@ -10,9 +10,8 @@
   COPTER_LED:        0x06
   COPTER_FLIP:       0x07
   COPTER_VIDEO:      0x08
-  COPTER_LAND:       0x09
-  COPTER_EMERGENCY:  0x0A
-  COPTER_DISCONNECT: 0x0B
+  COPTER_EMERGENCY:  0x09
+  COPTER_DISCONNECT: 0x0A

 COPTER_TYPES =
   HUBSAN_X4: 0x01


Reduce number of parallel requests. Fix reponse handling.

--- src/coffee/customerxmlimport.coffee
+++ src/coffee/customerxmlimport.coffee
@@ -72,7 +72,7 @@

       @processInBatches posts, callback

-  processInBatches: (posts, callback, numberOfParallelRequest = 50, acc = []) =>
+  processInBatches: (posts, callback, numberOfParallelRequest = 20, acc = []) =>
     current = _.take posts, numberOfParallelRequest
     Q.all(current).then (msg) =>
       messages = acc.concat(msg)
@@ -95,9 +95,9 @@
           @createPaymentInfo(customer, paymentInfo)
           @linkCustomerIntoGroup(customer, newCustomer.customerGroup)
         ]
-        Q.all(posts).fail (msg) =>
+        Q.all(posts).fail (msg) ->
           deferred.reject msg
-        .then (msg) =>
+        .then (msg) ->
           deferred.resolve "Customer created."

     deferred.promise
@@ -108,7 +108,7 @@
     @rest.POST '/customers', JSON.stringify(newCustomer), (error, response, body) ->
       if error
         deferred.reject 'Error on creating customer: ' + error
-      if response.statusCode is 201
+      else if response.statusCode is 201
         deferred.resolve JSON.parse(body).customer
       else
         deferred.reject 'Problem on creating customer: ' + body
@@ -130,7 +130,7 @@
     @rest.POST '/custom-objects', JSON.stringify(customObj), (error, response, body) ->
       if error
         deferred.reject 'Error on creating payment-info object: ' + error
-      if response.statusCode is 201
+      else if response.statusCode is 201
         deferred.resolve 'Payment info created.'
       else
         deferred.reject 'Problem on creating payment-info object: ' + body
@@ -155,7 +155,7 @@
     @rest.POST "/customers/#{customer.id}", JSON.stringify(data), (error, response, body) ->
       if error
         deferred.reject 'Error on linking customer with group: ' + error
-      if response.statusCode is 200
+      else if response.statusCode is 200
         deferred.resolve 'Customer linked to customer group.'
       else
         deferred.reject 'Problem on linking customer with group: ' + body
@@ -176,7 +176,7 @@
     @rest.POST "/customers/#{customer.id}", JSON.stringify(data), (error, response, body) ->
       if error
         deferred.reject 'Error on adding address: ' + error
-      if response.statusCode is 200
+      else if response.statusCode is 200
         deferred.resolve JSON.parse(body)
       else
         deferred.reject 'Problem on adding address: ' + body


now can validate fields

now can validate fields

--- src/coffee/directives/submit.coffee
+++ src/coffee/directives/submit.coffee
@@ -7,15 +7,17 @@
 ##

 angular.module('CoolFormDirectives').
-  directive('coolformSubmit', (submitService) ->
+  directive('coolformSubmit', ->

     l = (scope) ->
-      scope.submit =  -> submitService(scope)
+      scope.submit = ->
+        scope.service.validateAllFields()

     return {
       restrict: 'E'
       scope:
         definition: '='
+        service: '='
       template: templates.submit
       link: l
     }


more correct coffee-script

more correct coffee-script

--- models/mongo.coffee
+++ models/mongo.coffee
@@ -111,7 +111,7 @@
     updateAll: (query, cmd, fn) ->
       @prototype.collection (error, collection) ->
         return fn error if error?
-        collection.update Mongo.queryify(query), cmd, (upsert: false, multi: true), fn
+        collection.update Mongo.queryify(query), cmd, {upsert: false, multi: true}, fn

   InstanceMethods:
     collection: (fn) ->


Scan chocolatey lib dir for latest Atom version

--- src/config.coffee
+++ src/config.coffee
@@ -2,6 +2,7 @@
 fs = require './fs'
 path = require 'path'
 npm = require 'npm'
+semver = require 'semver'

 module.exports =
   getHomeDirectory: ->
@@ -36,22 +37,26 @@
               if fs.isDirectorySync(programFilesPath)
                 callback(programFilesPath)
               else
-                chocolateyAtomPath = @getChocolateyAtomPath()
-                child_process.exec "#{chocolateyAtomPath} --shimgen-waitforexit -v", (error, stdout='', stderr) ->
-                  output = stdout.split(' ')
-                  version = output[output.length - 1]
-                  if version
-                    if process.env.CHOCOLATEYINSTALL
-                      atomPath = path.join(process.env.CHOCOLATEYINSTALL, 'lib', "Atom.#{version}")
+                if process.env.CHOCOLATEYINSTALL
+                  chocolateyLibPath = path.join(process.env.CHOCOLATEYINSTALL, 'lib')

-                    if process.env.ALLUSERSPROFILE and not fs.isDirectorySync(atomPath)
-                      atomPath = path.join(process.env.ALLUSERSPROFILE, 'chocolatey', 'lib', "Atom.#{version}")
+                if process.env.ALLUSERSPROFILE and not fs.isDirectorySync(chocolateyLibPath)
+                  chocolateyLibPath = path.join(process.env.ALLUSERSPROFILE, 'chocolatey', 'lib')

-                    if fs.isDirectorySync(atomPath)
-                      appLocation = path.join(atomPath, 'tools', 'Atom', 'resources', 'app')
-                      callback(appLocation) if fs.isDirectorySync(appLocation)
+                latestVersion = null
+                try
+                  for child in fs.readdirSync(chocolateyLibPath)
+                    if child.indexOf('Atom.') is 0
+                      version = child.substring(5)
+                      if semver.valid(version)
+                        latestVersion ?= version
+                        latestVersion = version if semver.gt(version, latestVersion)

-                  callback(programFilesPath)
+                if latestVersion
+                  appLocation = path.join(chocolateyLibPath, "Atom.#{version}", 'tools', 'Atom', 'resources', 'app')
+                  return callback(appLocation) if fs.isDirectorySync(appLocation)
+
+                callback(programFilesPath)

   getReposDirectory: ->
     process.env.ATOM_REPOS_HOME ? path.join(@getHomeDirectory(), 'github')


spelling: visually

spelling: visually

--- src/config.coffee
+++ src/config.coffee
@@ -221,7 +221,7 @@
 # #### object / Grouping other types
 #
 # A config setting with the type `object` allows grouping a set of config
-# settings. The group will be visualy separated and has its own group headline.
+# settings. The group will be visually separated and has its own group headline.
 # The sub options must be listed under a `properties` key.
 #
 # ```coffee


:memo:

:memo:

--- src/config.coffee
+++ src/config.coffee
@@ -398,6 +398,14 @@
     value = @constructor.executeSchemaValidators(value, schema) if schema = @getSchema(keyPath)
     value

+# Base schema validators. These will coerce raw input into the specified type,
+# and will throw an error when the value cannot be coerced. Throwing the error
+# will indicate that the value should not be set.
+#
+# Validators are run from most specific to least. For a schema with type
+# `integer`, all the validators for the `integer` type will be run first, in
+# order of specification. Then the `*` validators will be run, in order of
+# specification.
 Config.addSchemaValidators
   'integer':
     coercion: (value, schema) ->


removed parsing logs

removed parsing logs

--- src/generic_reader.coffee
+++ src/generic_reader.coffee
@@ -8,8 +8,8 @@
     xhr(url,onret)

   @_onRetrieval: (text, callback) ->
-    start = new Date().getTime()
+    #start = new Date().getTime()
     rText = @parse(text)
-    end = new Date().getTime()
-    console.log "Parsing time: #{(end - start)} ms"
+    #end = new Date().getTime()
+    #console.log "Parsing time: #{(end - start)} ms"
     callback rText


Add value and width props to Range class

--- public/js/range.coffee
+++ public/js/range.coffee
@@ -1,3 +1,4 @@
 class App.Range
 	constructor: (@min, @max) ->
-		@values = [@min, @max]+		@values = [@min, @max]
+		@width = @max - @min


Change submitodownloader text

Change submitodownloader text

--- server/cron/downloadSubmits.coffee
+++ server/cron/downloadSubmits.coffee
@@ -222,7 +222,7 @@
         User.updateUser(uid, @dirtyResults)

     run: ->
-        logger.info "AllSubmitDownloader::run ", @userList, @submitsPerPage, @minPages, '-', @limitPages
+        logger.info "AllSubmitDownloader.run ", @userList, @submitsPerPage, @minPages, '-', @limitPages

         @adminUser = await InformaticsUser.findAdmin()

@@ -247,7 +247,7 @@
             logger.debug "Will process added user ", uid
             addedPromises.push(@processAddedUser(uid))
         await Promise.all(addedPromises)
-        logger.info "Finish AllSubmitDownloader::run ", @userList, @limitPages
+        logger.info "Finish AllSubmitDownloader.run ", @userList, @limitPages

 class LastSubmitDownloader extends AllSubmitDownloader
     needContinueFromSubmit: (runid) ->


onDidMoveCursor -> onDidChangeCursorPosition

--- src/editor.coffee
+++ src/editor.coffee
@@ -262,8 +262,8 @@
   #     * `newBufferPosition` {Point}
   #     * `newScreenPosition` {Point}
   #     * `textChanged` {Boolean}
-  onDidMoveCursor: (callback) ->
-    @emitter.on 'did-move-cursor', callback
+  onDidChangeCursorPosition: (callback) ->
+    @emitter.on 'did-change-cursor-position', callback

   # Extended: Calls your `callback` when a {Selection} is added to the editor.
   #
@@ -349,7 +349,7 @@
       when 'cursor-removed'
         deprecate("Use Editor::onDidRemoveCursor instead")
       when 'cursor-moved'
-        deprecate("Use Editor::onDidMoveCursor instead")
+        deprecate("Use Editor::onDidChangeCursorPosition instead")

       when 'selection-added'
         deprecate("Use Editor::onDidAddSelection instead")
@@ -1913,7 +1913,7 @@

   cursorMoved: (cursor, event) ->
     @emit 'cursor-moved', event
-    @emitter.emit 'did-move-cursor', event
+    @emitter.emit 'did-change-cursor-position', event

   # Merge cursors that have the same screen position
   mergeCursors: ->


Fix #120: do not call indexOf on undefined

--- src/jquery.payment.coffee
+++ src/jquery.payment.coffee
@@ -439,9 +439,10 @@
   cvc = $.trim(cvc)
   return false unless /^\d+$/.test(cvc)

-  if type
+  card = cardFromType(type)
+  if card?
     # Check against a explicit card type
-    cvc.length in cardFromType(type)?.cvcLength
+    cvc.length in card.cvcLength
   else
     # Check against all types
     cvc.length >= 3 and cvc.length <= 4


Clean up database master require.

Clean up database master require.

--- lib/db.coffee
+++ lib/db.coffee
@@ -24,9 +24,5 @@
 #   end: ->

 config = require './config'
-
-key_value = require "./adapters/#{config.adapters.key_value}/key_value"
-pub_sub = require "./adapters/#{config.adapters.pub_sub}/pub_sub"
-queue = require "./adapters/#{config.adapters.queue}/queue"
-
-module.exports = {key_value, pub_sub, queue}
+for type, adapter of config.adapters
+  exports[type] = require "./adapters/#{adapter}/#{type}"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine.coffee
+++ src/engine.coffee
@@ -144,8 +144,8 @@

     step = ->
       if !I.paused || frameAdvance
-        elapsedTime =
-        secondsPerFrame = (1 / I.FPS)
+        elapsedTime = (1 / I.FPS)
+        secondsPerFrame =
         update(secondsPerFrame)
         I.age += 1


Cleanup

Cleanup

--- src/entity.coffee
+++ src/entity.coffee
@@ -364,7 +364,8 @@

   # Collision from any side with another entity. Returns Boolean
   intersecting: (ent) ->
-    @leftCollision(ent) || @rightCollision(ent) || @topCollision(ent) || @bottomCollision(ent)
+    @bottomCollision(ent) || @leftCollision(ent) || @rightCollision(ent) || @topCollision(ent)
+

   # Is this entity standing on a surface?
@@ -438,8 +439,7 @@
     @currentAnimation = @animations[name] or fail("#{@class()}#setAnimation", "Couldn't find animation with name #{name}")

-  clearAnimation: ->
-    @currentAnimation = null
+  clearAnimation: -> @currentAnimation = null


Fix md.coffee typo

Fix md.coffee typo


Improve github fuzzy matching results

Improve github fuzzy matching results

--- src/github.coffee
+++ src/github.coffee
@@ -46,7 +46,10 @@
     robot.brain.set 'github-notifications', notifications

   lookupUserWithGithub = (github) ->
-    if github
+    return if not github
+
+    github.fetch().then (user) ->
+      name = user.name or github.login
       users = robot.brain.users()
       users = _(users).keys().map (id) ->
         u = users[id]
@@ -58,10 +61,10 @@
         keys: ['real_name']
         shouldSort: yes
         verbose: no
-      results = f.search github.login
-      result = if results? and results.length >=1 then results[0] else null
-      return result if result
-    return null
+
+      results = f.search name
+      result = if results? and results.length >=1 then results[0] else undefined
+      return result

   notificationShouldFire = (notification) ->
     now = new Date
@@ -123,19 +126,28 @@
       return

     repo = octo.repos(githubOrg, repo)
-    repo.pulls.fetch(state: "open").then (prs) ->
+    repo.pulls.fetch(state: "open")
+    .then (prs) ->
       return Promise.all prs.map (pr) ->
         if user?
           return if not pr.assignee?
-          assignee = lookupUserWithGithub pr.assignee
-          return if user.toLowerCase() isnt assignee.name.toLowerCase()
-
-        return repo.pulls(pr.number).fetch()
-    .then ( prs ) ->
-      message = ""
-      for pr in prs when pr
+          return lookupUserWithGithub(pr.assignee).then (assignee) ->
+            return if user.toLowerCase() isnt assignee?.name.toLowerCase()
+            return repo.pulls(pr.number).fetch()
+        else
+          return repo.pulls(pr.number).fetch()
+    .then (prs) ->
+      return Promise.all prs.map (pr) ->
+        return if not pr
         author = lookupUserWithGithub pr.user
         assignee = lookupUserWithGithub pr.assignee
+        return Promise.all [ pr, author, assignee ]
+    .then (prs) ->
+      message = ""
+      for p in prs when p
+        pr = p[0]
+        author = p[1]
+        assignee = p[2]
         message+= """
           *[#{pr.title}]* +#{pr.additions} -#{pr.deletions}
           #{pr.htmlUrl}
@@ -150,7 +162,7 @@

       robot.messageRoom room, message
     .catch ( error ) ->
-      console.log "Error: #{error}"
+      console.log error.stack

   robot.respond /(?:github|gh|git) delete all notifications/i, (msg) ->
     notificationsCleared = clearAllNotificationsForRoom(findRoom(msg))


don't clear user defined progress on done

--- lib/ui.coffee
+++ lib/ui.coffee
@@ -27,5 +27,8 @@
     @views.ink = @ink
     @progress.ink = @ink

-    @subs.add @client.onWorking => @progress.add progress: null
-    @subs.add @client.onDone    => @progress.clear()
+    [status] = []
+
+    @subs.add @client.onWorking  => status = @progress.add progress: null
+    @subs.add @client.onDone     => status?.destroy()
+    @subs.add @client.onDetached => @progress.clear()


Allow html for rendered notices

Allow html for rendered notices

--- assets/js/intertwinkles/notifications.coffee
+++ assets/js/intertwinkles/notifications.coffee
@@ -16,7 +16,7 @@
             <% } %>

-
<%- notice.formats.web %>

+
<%= notice.formats.web %>

<span class='date' data-date='<%- notice.date %>'>


Minor code cleanup

Minor code cleanup

--- client/app/components/message-list.coffee
+++ client/app/components/message-list.coffee
@@ -318,13 +318,17 @@
                         conversationID = message.get 'conversationID'
                         ConversationActionCreator.delete conversationID, (error) ->
                             if error?
-                                alertError "#{t("conversation move ko")} #{error}"
+                                alertError "#{t("conversation delete ko")} #{error}"
                             else
                                 window.cozyMails.messageNavigate()
             else
                 if (not @props.settings.get('messageConfirmDelete')) or
                 window.confirm(t 'list delete confirm', smart_count: selected.length)
-                    MessageActionCreator.delete selected
+                    MessageActionCreator.delete selected, (error) ->
+                        if error?
+                            alertError "#{t("message action delete ko")} #{error}"
+                        else
+                            window.cozyMails.messageNavigate()

     onMove: (args) ->
         selected = Object.keys @state.selected
@@ -390,11 +394,11 @@
                     when 'seen'
                         ConversationActionCreator.seen conversationID, (error) ->
                             if error?
-                                alertError "#{t("conversation seen ok ")} #{error}"
+                                alertError "#{t("conversation seen ko ")} #{error}"
                     when 'unseen'
                         ConversationActionCreator.unseen conversationID, (error) ->
                             if error?
-                                alertError "#{t("conversation unseen ok")} #{error}"
+                                alertError "#{t("conversation unseen ko")} #{error}"

     expungeMailbox: (e) ->
         e.preventDefault()


client: remove mockHttpClient util

client: remove mockHttpClient util

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/app/lib/util/mockHttpClient.coffee
+++ client/app/lib/util/mockHttpClient.coffee
@@ -1,11 +0,0 @@
-MockAdapter = require 'axios-mock-adapter'
-
-module.exports = mockHttpClient = (client, endpointMocks = []) ->
-
-  mock = new MockAdapter client
-
-  endpointMocks.forEach ([endpoint, mock]) ->
-    mock.onAny(endpoint).reply 200, mock
-
-  return mock
-


Don't persist changes when close icon is clicked

--- src/course/modify-registration.cjsx
+++ src/course/modify-registration.cjsx
@@ -27,9 +27,12 @@
       _.delay(@onComplete, 1500)
     @forceUpdate()

+  showTasks: ->
+    Navigation.channel.emit('show.panel', view: 'task')
+
   onComplete: ->
     @state.course.persist(User)
-    Navigation.channel.emit('show.panel', view: 'task')
+    @showTasks()

   renderComplete: (course) ->

@@ -52,7 +55,7 @@

   render: ->

-
+
       {@renderCurrentStep()}


REVERT - make sure everyone is using the same Neptune even if they are including two different versions of the lib

--- source/Runtime/Neptune.coffee
+++ source/Runtime/Neptune.coffee
@@ -1,6 +1,6 @@
 NamespaceBaseClass = require './NamespaceBaseClass'

-global.Neptune ||= module.exports = class Neptune extends NamespaceBaseClass
+module.exports = global.Neptune ||= class Neptune extends NamespaceBaseClass
   @Base: NamespaceBaseClass
   @namespacePath: "Neptune"
   @namespace: null


Fix incorrect audio source being set on stop

--- resources/assets/coffee/_classes/osu-audio.coffee
+++ resources/assets/coffee/_classes/osu-audio.coffee
@@ -49,7 +49,7 @@
   stop: =>
     @player().pause()
     @player().currentTime = 0
-    @urlSet ''
+    @urlRemove()
     @publish 'ended'

@@ -59,3 +59,7 @@

   urlSet: (url) =>
     @player().setAttribute 'src', url
+
+
+  urlRemove: =>
+    @player().removeAttribute 'src'


Adding an option to the command line interface to output to a javascript file

--- source/coffee/compiler.coffee
+++ source/coffee/compiler.coffee
@@ -14,5 +14,8 @@
 fs = require 'fs'
 assert = require 'assert'
 code = exports.compile fs.readFileSync process.argv[2] if process.argv[2]?
-console.log code
-console.log eval(code)+if process.argv[3]?
+  fs.writeFileSync process.argv[3], code
+else
+  console.log code
+  console.log eval(code)


static pages: prerender urls whitelist

static pages: prerender urls whitelist

--- deployment/nginx.coffee
+++ deployment/nginx.coffee
@@ -303,14 +303,27 @@
         proxy_set_header      X-Prerender-Token #{KONFIG.prerenderToken};

         set $prerender 0;
+        set $validation_result '';
         if ($http_user_agent ~* "baiduspider|twitterbot|facebookexternalhit|rogerbot|linkedinbot|embedly|quora link preview|showyoubot|outbrain|pinterest|slackbot|vkShare|W3C_Validator") {
+          set $validation_result 'valid bot';
+        }
+        if ($args ~ "_escaped_fragment_") {
+          set $validation_result 'valid bot';
+        }
+        if ($request_uri ~ "\/(Pricing|About|Legal|Features)(\/|&|\?|$)") {
+          set $validation_result "${validation_result} & valid page";
+        }
+        if ($args ~ '_escaped_fragment_=(&|$)') {
+          set $validation_result "${validation_result} & valid page";
+        }
+        if ($request_uri ~ "^(\/)?$") {
+          set $validation_result "${validation_result} & valid page";
+        }
+        if ($http_user_agent ~ "Prerender") {
+          set $validation_result '';
+        }
+        if ($validation_result = 'valid bot & valid page') {
           set $prerender 1;
-        }
-        if ($args ~ "_escaped_fragment_") {
-          set $prerender 1;
-        }
-        if ($http_user_agent ~ "Prerender") {
-          set $prerender 0;
         }

         #resolve using Google's DNS server to force DNS resolution and prevent caching of IPs


Block render: Fixed a bug where the level was not set correctly. See #47702

--- src/elements/Form/SimpleForm.coffee
+++ src/elements/Form/SimpleForm.coffee
@@ -354,7 +354,7 @@
 					append(get_append(field._form.right), hint_div)

 			if field.renderAsBlock()
-				level = parseInt(CUI.dom.getAttribute(@DOM, "cui-form-depth"))+1
+				level = field.getFormDepth() + 1
 				if not level
 					level = 1


fix dir crash

fix dir crash

--- asterisk.coffee
+++ asterisk.coffee
@@ -104,7 +104,7 @@

     open = (req) ->
         print "open", req.filename
-        if fs.existsSync(req.filename)
+        if fs.statSync(req.filename).isFile()
             file_data = fs.readFileSync(req.filename, 'utf8')
             socket.emit 'open-push',
                 filename: req.filename


disiable of pylint

disiable of pylint

--- asterisk.coffee
+++ asterisk.coffee
@@ -95,7 +95,7 @@

 pylint = (s, filename) ->
     print "running lint", s, filename
-    command = "pylint #{filename} -f parseable -r n -d W0621,C0111,C0103,W0403"
+    command = "pylint #{filename} -f parseable -r n -d W0621,C0111,C0103,W0403,R0911,R0912,R0913,R0914"
     exec command, (error, stdout, stderr) ->
         marks = for line in stdout.split("\n")
             m = line.match(/.*:(\d*):\s\[(.*)\]\s(.*)/)


When deleting a book, avoid re-editing the same book.

This happens because the removeMe call becomes async on account of an
on-the-fly dependency to epub-container causing the delete to happen in
a callback, which means that when we select the next book to open, the old
one hasn't been deleted yet.

--- scripts/views/workspace/sidebar/toc-branch.coffee
+++ scripts/views/workspace/sidebar/toc-branch.coffee
@@ -1,12 +1,13 @@
 define [
   'jquery'
+  'underscore'
   'marionette'
   'cs!gh-book/opf-file'
   'cs!controllers/routing'
   'cs!helpers/enable-dnd'
   'cs!collections/content'
   'hbs!templates/workspace/sidebar/toc-branch'
-], ($, Marionette, OpfFile, controller, EnableDnD, allContent, tocBranchTemplate) ->
+], ($, _, Marionette, OpfFile, controller, EnableDnD, allContent, tocBranchTemplate) ->

   # This class introduces a `renderModelOnly()` method that will
   # re-render only the Model part of the CompositeView.
@@ -226,9 +227,13 @@
             next = parent.findDescendantDFS((model) -> return model.getChildren().isEmpty())
             controller.goEdit(next, root)
           else
-            # redirect to first book with no module selected, if there are no more books we
-            # go to the "workspace"
-            firstBook = allContent.findWhere({mediaType: OpfFile::mediaType})
+            # redirect to first book. The .without() is necessary because
+            # OpfFile::removeMe requires EpubContainer on the fly and this
+            # causes the delete operation to be async, which means at this
+            # point in the code the book might not be deleted yet, which breaks
+            # things when you delete the first book.
+            firstBook = _.findWhere allContent.without(this.model),
+              mediaType: OpfFile.prototype.mediaType
             controller.goEdit(firstBook, firstBook)

     goEdit: () ->


Fix autocomplete-manager-integration-spec

--- spec/autocomplete-manager-integration-spec.coffee
+++ spec/autocomplete-manager-integration-spec.coffee
@@ -361,7 +361,7 @@

         runs ->
           expect(editorView.querySelector('.autocomplete-plus')).toExist()
-          editor.insertText(' ')
+          editor.insertText('\r')
           waitForAutocomplete()

         runs ->
@@ -396,7 +396,7 @@

         runs ->
           expect(editorView.querySelector('.autocomplete-plus')).toExist()
-          editor.insertText(' ')
+          editor.insertText('\r')
           waitForAutocomplete()

         runs ->
@@ -484,7 +484,7 @@
           editor.moveToBottom()
           editor.insertText('f')
           editor.insertText('u')
-          editor.insertText(' ')
+          editor.insertText('\r')

           waitForAutocomplete()


Make naked fraction and root buttons a little nicer

--- src/javascripts/math_buttons.coffee
+++ src/javascripts/math_buttons.coffee
@@ -94,7 +94,7 @@
   fraction: (opts)->
     @button({
       value: 'fraction'
-      label: "a/b"
+      label: "a/b"
       class: 'jc--button jc--button-other jc--button-fraction'
     }, opts)

@@ -121,7 +121,7 @@
     }, opts)

   root: (opts)->
-    degree = if opts.degree then "#{opts.degree}
" else ""
+    degree = if opts.degree then "#{opts.degree}" else ""
     radicand = opts.radicand || 'x'

     @button({


:bug: Now space fire refresh to cover space to split word into two

--- lib/ui.coffee
+++ lib/ui.coffee
@@ -738,9 +738,6 @@
       {newRange, oldRange, newText, oldText} = event
       return if @ignoreChange

-      # Ignore white spaces change
-      return if oldText.trim() is newText.trim()
-
       promptRange = @getPromptRange()
       onPrompt = (range) -> range.intersectsWith(promptRange)
       isQueryModified = (newRange, oldRange) ->


:tada: now sync-able fro all providers so I no longer lost where am I!

--- lib/ui.coffee
+++ lib/ui.coffee
@@ -5,6 +5,7 @@
   isActiveEditor
   getValidIndexForList
   setBufferRow
+  isTextEditor
 } = require './utils'
 settings = require './settings'
 Grammar = require './grammar'
@@ -104,17 +105,28 @@
     @disposables.add(@observeStopChanging())
     @disposables.add(@observeCursorMove())

+    if @provider.boundToEditor
+      @disposables.add @provider.editor.onDidDestroy(@destroy.bind(this))
+
     @disposables.add atom.workspace.onDidStopChangingActivePaneItem (item) =>
-      unless item is @editor
-        @rowMarker?.destroy()
-
-    if @provider.boundToEditor
-      @setSyncToEditor(@provider.editor)
-      @disposables.add @provider.editor.onDidDestroy(@destroy.bind(this))
+      @syncSubcriptions?.dispose()
+      return if item is @editor
+      @rowMarker?.destroy()
+
+      if isTextEditor(item) and @canSyncToEditor(item)
+        @syncToEditor(item)
+        @setSyncToEditor(item)

     @constructor.register(this)
     @disposables.add new Disposable =>
       @constructor.unregister(this)
+
+  canSyncToEditor: (editor) ->
+    filePath = editor.getPath()
+    if @provider.boundToEditor
+      @provider.editor.getPath() is filePath
+    else
+      @items.some (item) -> item.filePath is filePath

   start: ->
     activatePaneItemInAdjacentPane(@editor, split: settings.get('directionToOpen'))
@@ -375,6 +387,7 @@
     if @provider.boundToEditor
       items = _.reject(@items, (item) -> item.skip)
     else
+      # Item must support filePath
       filePath = editor.getPath()
       items = @items.filter((item) -> not item.skip and (item.filePath is filePath))

@@ -394,7 +407,11 @@
     if (row = @getRowForSelectedItem()) >= 0
       oldPosition = @editor.getCursorBufferPosition()
       @withIgnoreCursorMove =>
-        @editor.setCursorBufferPosition([row, oldPosition.column])
+        # Manually set cursor to center to avoid scrollTop drastically changes
+        # when refresh and auto-sync.
+        point = [row, oldPosition.column]
+        @editor.setCursorBufferPosition(point, autoscroll: false)
+        @editor.scrollToBufferPosition(point, center: true)
         @emitDidMoveToItemArea() if @isPromptRow(oldPosition.row)

   setRowMarker: (editor, point) ->
@@ -497,15 +514,7 @@
     range

   setSyncToEditor: (editor) ->
-    @syncSubcriptions?.dispose()
     @syncSubcriptions = new CompositeDisposable
-    @syncSubcriptions.add atom.workspace.onDidStopChangingActivePaneItem (item) =>
-      @syncToEditor(editor) if item is editor
-
-    @syncSubcriptions.add editor.onDidStopChanging =>
-      # Skip is not activeEditor, important to skip auto-refresh on direct-edit.
-      @refresh(force: true) if isActiveEditor(editor)
-
     @syncSubcriptions.add editor.onDidChangeCursorPosition (event) =>
       if isActiveEditor(editor) and
           (not event.textChanged) and
@@ -515,6 +524,14 @@
     @syncSubcriptions.add @onDidRefresh =>
       @syncToEditor(editor) unless @isActive()

+    if @provider.boundToEditor
+      @syncSubcriptions.add editor.onDidStopChanging =>
+        # Surppress refreshing while editor is active to avoid auto-refreshing while direct-edit.
+        @refresh(force: true) if isActiveEditor(editor)
+    else
+      @syncSubcriptions.add editor.onDidSave =>
+        @refresh(force: true) unless @isActive()
+
   # vim-mode-plus integration
   # -------------------------
   vmpActivateNormalMode: ->


update.

update.

--- src/main/public/nz/ai/Chaser.coffee
+++ src/main/public/nz/ai/Chaser.coffee
@@ -16,6 +16,29 @@
   setupAction: (o) -> @ruleAI.setupAction o
   rule: (o) -> @ruleAI.add o

+  calcSlidePosition: (param) ->
+    {
+      target
+      character
+      length
+    } = param
+    m = length * 32
+    # ターゲット
+    t = tm.geom.Vector2().setObject nz.utils.mapxy2screenxy target
+    # 自分の位置
+    s = tm.geom.Vector2().setObject nz.utils.mapxy2screenxy character
+    # ターゲットまでの距離
+    d = s.distance t
+    # ターゲットからの方向
+    r = s.sub(t).toAngle()
+    # 移動後の方向
+    r += m / d
+    # 目的値
+    g = tm.geom.Vector2().setRadian(r,d).add(t)
+    a = nz.utils.screenxy2mapxy g
+    #console.log "t=#{t} s=#{s} d=#{d} r=#{r} g=#{g} a.mapx=#{a.mapx} a.mapy=#{a.mapy}"
+    return a
+
   ###* 初期化
   * @classdesc サンプルAIクラス
   * @constructor nz.ai.Chaser
@@ -31,6 +54,23 @@
         return false
     @rule
       cond: (param) ->
+        p = @calcSlidePosition
+          target: param.target
+          character: param.character
+          length: 4
+        if param.checkMovePosition p
+          return true
+        return false
+      setup: (param) ->
+        p = @calcSlidePosition
+          target: param.target
+          character: param.character
+          length: 4
+        param.setAttackCommand()
+        param.setMoveCommand(target:p)
+        return true
+    @rule
+      cond: (param) ->
         # どれでもない時
         return true
       setup: (param) ->


Started working on web_server class.

Started working on web_server class.

--- start.coffee
+++ start.coffee
@@ -2,10 +2,37 @@
 fs		= require "fs"
 async		= require "async"
 util		= require "util"
-find		= require "find"
 http		= require "http"
 express		= require "express"
 sylvester	= require "sylvester"
+
+class web_server
+	constructor: ( @port, @static_dir ) ->
+		# Setup the initial application ( just express ).
+		@app = express( )
+
+		# Basic express middleware.
+		@app.use express.logger( )
+		@app.use express.static @static_dir
+
+	_error_out: ( res, err ) ->
+		# Helper function for sending an error back.
+		res.json { "error": err }
+
+	start: ( cb ) ->
+		if @server?
+			return cb "Already started"
+
+		@server = http.createServer @app
+		@server.listen @port, ( ) ->
+			return cb null
+
+	stop: ( cb ) ->
+		if not @server?
+			return cb "Not started"
+
+		@server.close ( ) ->
+			return cb null

 config	= { }
 runtime	= { }


Add several scroll shims to ReactEditorView

--- src/react-editor-view.coffee
+++ src/react-editor-view.coffee
@@ -65,11 +65,21 @@
     else
       @editor.getScrollLeft()

+  scrollToBottom: ->
+    @editor.setScrollBottom(Infinity)
+
   scrollToScreenPosition: (screenPosition) ->
     @editor.scrollToScreenPosition(screenPosition)

   scrollToBufferPosition: (bufferPosition) ->
     @editor.scrollToBufferPosition(bufferPosition)
+
+  scrollToCursorPosition: ->
+    @editor.scrollToCursorPosition()
+
+  scrollToPixelPosition: (pixelPosition) ->
+    screenPosition = screenPositionForPixelPosition(pixelPosition)
+    @editor.scrollToScreenPosition(screenPosition)

   pixelPositionForBufferPosition: (bufferPosition) ->
     @editor.pixelPositionForBufferPosition(bufferPosition)


Only allow ReactEditorView::beforeRemove to run once

--- src/react-editor-view.coffee
+++ src/react-editor-view.coffee
@@ -122,8 +122,9 @@
     @find('.lines').prepend(view)

   beforeRemove: ->
+    return unless @attached
+    @attached = false
     React.unmountComponentAtNode(@element) if @component.isMounted()
-    @attached = false
     @trigger 'editor:detached', this

   # Public: Split the editor view left.


Fix the mini-editor test

Fix the mini-editor test

--- src/react-editor-view.coffee
+++ src/react-editor-view.coffee
@@ -39,9 +39,9 @@
     if editorOrParams instanceof Editor
       @editor = editorOrParams
     else
-      {@editor, mini, placeholderText} = editorOrParams
+      {@editor, @mini, placeholderText} = editorOrParams
       props ?= {}
-      props.mini = mini
+      props.mini = @mini
       props.placeholderText = placeholderText
       @editor ?= new Editor
         buffer: new TextBuffer


add timeline for counties

add timeline for counties

--- source/javascripts/all.coffee
+++ source/javascripts/all.coffee
@@ -43,6 +43,20 @@

     autoProjectTo(nation)

+    # Create a timeline array from the recruits over the years and remove
+    # one-star recruits from the total count because we're not that interested
+    # in one-star recruits and it's highly likely that we don't have all of the
+    # one star recruits in the database.
+    for county in counties.features
+      props = county.properties
+      props.timeline = []
+      if props.total
+        props.total -= county.properties.one_star
+        for year in [2002..2013] by 1
+          props.timeline.push
+            year: year
+            count: props["total_#{year}"]
+
     env = {states, counties, nation, schools, recruits, places, projection, path, fill, colors, width, height}
     $(document).trigger 'data.loaded', env


new total syntax

new total syntax

--- source/javascripts/all.coffee
+++ source/javascripts/all.coffee
@@ -54,7 +54,7 @@
       if props.total
         props.total -= county.properties.one_star
         for year in [2002..2015] by 1
-          count = props["total_#{year}"] || 0
+          count = props["t#{year}"] || 0
           maxyear = count if count > maxyear
           props.timeline.push {year, count}


fixed jshint warnings

fixed jshint warnings

--- src/scripts/directive.coffee
+++ src/scripts/directive.coffee
@@ -56,7 +56,7 @@
               (datasource) ->

                 bufferSize = Math.max(3, +$attr.bufferSize || 10)
-                bufferPadding = -> viewport.height() * Math.max(.2, +$attr.padding || .5) # some extra space to initate preload in advance
+                bufferPadding = -> viewport.height() * Math.max(0.2, +$attr.padding || 0.5) # some extra space to initate preload in advance

                 controller = null


Remove line break in config docs

Remove line break in config docs

--- src/scripts/http-info.coffee
+++ src/scripts/http-info.coffee
@@ -8,8 +8,7 @@
 # Configuration:
 #   HUBOT_HTTP_INFO_IGNORE_URLS - RegEx used to exclude Urls
 #   HUBOT_HTTP_INFO_IGNORE_USERS - Comma-separated list of users to ignore
-#   HUBOT_HTTP_INFO_IGNORE_DESC - Optional boolean indicating whether a site's
-#     meta description should be ignored, thus returning only a title
+#   HUBOT_HTTP_INFO_IGNORE_DESC - Optional boolean indicating whether a site's meta description should be ignored
 #
 # Commands:
 #   http(s)://<site> - prints the title and meta description for sites linked.
@@ -58,4 +57,4 @@

             else if title
               msg.send "#{title}"
-        )+        )


support staging development and production environments

--- src/scripts/stagehand.coffee
+++ src/scripts/stagehand.coffee
@@ -8,23 +8,39 @@
 #   None
 #
 # Commands:
-#   stagehand who - Show who has booked the staging server and how much time they have left
-#   stagehand book [minutes] - Book the staging server and optionally specify usage time. Default is 30min
-#   stagehand cancel - Cancel the current booking
+#   stagehand who [env] - Show who has booked the staging server and how much time they have left
+#   stagehand book [env] [minutes] - Book the staging server and optionally specify usage time. Default is 30min
+#   stagehand cancel [env] - Cancel the current booking
 #
 # Author:
 #   tinifni

-bookStaging = (data, user, minutes) ->
+class Message
+  constructor: (env, minutes) ->
+    @env = env
+    @minutes = minutes
+  getEnv: ->
+    if @env == undefined
+      return 'staging'
+    else
+      return @env
+
+  getMinutes: ->
+    if @minutes == undefined
+      return 30
+    else
+      return Number(@minutes)
+
+bookEnv = (data, user, minutes) ->
   return false if data.user != user && new Date() < data.expires
   unless data.user == user && new Date() < data.expires
     data.user = user
     data.expires = new Date()
   data.expires = new Date(data.expires.getTime() + minutes * 1000 * 60)

-status = (data) ->
-  return 'Staging is free for use.' unless new Date() < data.expires
-  data.user + ' has staging booked for the next ' \
+status = (env, data) ->
+  return env + ' is free for use.' unless new Date() < data.expires
+  data.user + ' has ' + env + ' booked for the next ' \
             + Math.ceil((data.expires - new Date())/(60*1000)) \
             + ' minutes.'

@@ -33,19 +49,27 @@

 module.exports = (robot) ->
   robot.brain.on 'loaded', =>
-    robot.brain.data.stagehand ||= { user: "initial", expires: new Date(0) }
+    for env in ['staging', 'development', 'production']
+      do (env) ->
+        robot.brain.data.stagehand[env] ||= { user: "initial", expires: new Date(0) }

-  robot.respond /stagehand book( \d+)*/i, (msg) ->
-    if msg.match[1] == undefined
-      minutes = 30
-    else
-      minutes = Number(msg.match[1])
-    bookStaging(robot.brain.data.stagehand, msg.message.user.name, minutes)
-    msg.send status(robot.brain.data.stagehand)
+  robot.respond /stagehand book\s?([A-Za-z]+)*\s?(\d+)*/i, (msg) ->
+    message = new Message(msg.match[1], msg.match[2])
+    env = message.getEnv()
+    minutes = message.getMinutes()

-  robot.respond /stagehand who/i, (msg) ->
-    msg.send status(robot.brain.data.stagehand)
+    bookEnv(robot.brain.data.stagehand[env], msg.message.user.name, minutes)
+    msg.send status(env, robot.brain.data.stagehand[env])

-  robot.respond /stagehand cancel/i, (msg) ->
-    cancelBooking(robot.brain.data.stagehand)
-    msg.send status(robot.brain.data.stagehand)
+  robot.respond /stagehand who\s?([A-Za-z]+)*/i, (msg) ->
+    message = new Message(msg.match[1])
+    env = message.getEnv()
+
+    msg.send status(env, robot.brain.data.stagehand[env])
+
+  robot.respond /stagehand cancel\s?([A-Za-z]+)*/i, (msg) ->
+    message = new Message(msg.match[1])
+    env = message.getEnv()
+
+    cancelBooking(robot.brain.data.stagehand[env])
+    msg.send status(env, robot.brain.data.stagehand[env])


remove scrolling flag

remove scrolling flag

--- src/scripts/ui-scroll.coffee
+++ src/scripts/ui-scroll.coffee
@@ -173,7 +173,7 @@
 							pending = []
 							eof = false
 							bof = false
-							adjustBuffer(ridActual, false)
+							adjustBuffer(ridActual)

 						bottomVisiblePos = ->
 							viewport.scrollTop() + viewport.outerHeight()
@@ -234,12 +234,12 @@
 								first += overage
 								log "clipped off top #{overage} top padding #{adapter.topPadding()}"

-						enqueueFetch = (rid, direction, scrolling)->
+						enqueueFetch = (rid, direction)->
 							if (!isLoading)
 								isLoading = true
 								loading(true)
 							if pending.push(direction) == 1
-								fetch(rid, scrolling)
+								fetch(rid)

 						hideElementBeforeAppend = (element) ->
 							element.displayTemp = element.css('display')
@@ -289,12 +289,12 @@
 									# if not, increment scrollTop
 									viewport.scrollTop(viewport.scrollTop() + wrapper.element.outerHeight(true))

-						doAdjustment = (rid, scrolling, finalize)->
+						doAdjustment = (rid, finalize)->
 							log "top {actual=#{adapter.topDataPos()} visible from=#{topVisiblePos()} bottom {visible through=#{bottomVisiblePos()} actual=#{adapter.bottomDataPos()}}"
 							if shouldLoadBottom()
-								enqueueFetch(rid, true, scrolling)
-							else
-								enqueueFetch(rid, false, scrolling) if shouldLoadTop()
+								enqueueFetch(rid, true)
+							else
+								enqueueFetch(rid, false) if shouldLoadTop()
 							finalize(rid) if finalize
 							if pending.length == 0
 								topHeight = 0
@@ -309,7 +309,7 @@
 										topVisible(item) if newRow
 										break

-						adjustBuffer = (rid, scrolling, newItems, finalize)->
+						adjustBuffer = (rid, newItems, finalize)->
 							if newItems and newItems.length
 								$timeout ->
 									rows = []
@@ -322,25 +322,25 @@
 											rowTop = itemTop
 									for row in rows
 										adjustRowHeight(row.appended, row.wrapper)
-									doAdjustment(rid, scrolling, finalize)
-							else
-								doAdjustment(rid, scrolling, finalize)
-
-						finalize = (rid, scrolling, newItems)->
-							adjustBuffer rid, scrolling, newItems, ->
+									doAdjustment(rid, finalize)
+							else
+								doAdjustment(rid, finalize)
+
+						finalize = (rid, newItems)->
+							adjustBuffer rid, newItems, ->
 								pending.shift()
 								if pending.length == 0
 									isLoading = false
 									loading(false)
 								else
-									fetch(rid, scrolling)
-
-						fetch = (rid, scrolling) ->
+									fetch(rid)
+
+						fetch = (rid) ->
 							direction = pending[0]
 							#log "Running fetch... #{{true:'bottom', false: 'top'}[direction]} pending #{pending.length}"
 							if direction
 								if buffer.length && !shouldLoadBottom()
-									finalize(rid, scrolling)
+									finalize(rid)
 								else
 									#log "appending... requested #{bufferSize} records starting from #{next}"
 									datasource.get next, bufferSize,
@@ -356,10 +356,10 @@
 											for item in result
 												newItems.push (insert ++next, item)
 											#log "appended: requested #{bufferSize} received #{result.length} buffer size #{buffer.length} first #{first} next #{next}"
-										finalize(rid, scrolling, newItems)
+										finalize(rid, newItems)
 							else
 								if buffer.length && !shouldLoadTop()
-									finalize(rid, scrolling)
+									finalize(rid)
 								else
 									#log "prepending... requested #{size} records starting from #{start}"
 									datasource.get first-bufferSize, bufferSize,
@@ -375,21 +375,15 @@
 											for i in [result.length-1..0]
 												newItems.unshift (insert --first, result[i])
 											#log "prepended: requested #{bufferSize} received #{result.length} buffer size #{buffer.length} first #{first} next #{next}"
-										finalize(rid, scrolling, newItems)
-
-						resizeHandler = ->
+										finalize(rid, newItems)
+
+						resizeAndResizeHandler = ->
 							if !$rootScope.$$phase && !isLoading
-								adjustBuffer(null, false) #todo dhilt : is null rid passing safe for resize, scroll, delete and insert cases?
+								adjustBuffer()
 								$scope.$apply()

-						viewport.bind 'resize', resizeHandler
-
-						scrollHandler = ->
-							if !$rootScope.$$phase && !isLoading
-								adjustBuffer(null, true)
-								$scope.$apply()
-
-						viewport.bind 'scroll', scrollHandler
+						viewport.bind 'resize', resizeAndResizeHandler
+						viewport.bind 'scroll', resizeAndResizeHandler

 						wheelHandler = (event) ->
 							scrollTop = viewport[0].scrollTop
@@ -440,7 +434,7 @@
 									next--

 							item.scope.$index = first + i for item,i in buffer
-							adjustBuffer(null, false)
+							adjustBuffer()

 						eventListener.$on "insert.item", (event, locator, item)->
 							inserted = []
@@ -464,7 +458,7 @@
 									next++

 							item.scope.$index = first + i for item,i in buffer
-							adjustBuffer(null, false, inserted)
+							adjustBuffer(null, inserted)

 		])


Change assertions.

Change assertions.

--- spec/communicator_spec.coffee
+++ spec/communicator_spec.coffee
@@ -44,16 +44,16 @@
       @initStub.restore()

     it 'is not busy by default', ->
-      expect(@communicator.busy()).to.be_false
+      expect(@communicator.busy()).to.equal false

     it 'returns the current state of the property, or sets it', ->
       @communicator.busy(yes)
-      expect(@communicator.busy()).to.be_true
+      expect(@communicator.busy()).to.equal true

       @communicator.busy(no)
-      expect(@communicator.busy()).to.be_false
-
-      expect(@communicator.busy(yes)).to.be_true
+      expect(@communicator.busy()).to.equal false
+
+      expect(@communicator.busy(yes)).to.equal true

   describe '#isLocked', ->
     beforeEach ->
@@ -67,12 +67,12 @@
     it 'returns true if the plugin is locked', ->
       sinon.stub(@plugin, 'Locked').returns true
       @communicator.plugin = @plugin
-      expect(@communicator.isLocked()).to.be_true
+      expect(@communicator.isLocked()).to.equal true

     it 'returns false if the plugin is unlocked', ->
       sinon.stub(@plugin, 'Locked').returns false
       @communicator.plugin = @plugin
-      expect(@communicator.isLocked()).to.be_false
+      expect(@communicator.isLocked()).to.equal false

   describe '#unlock', ->
     beforeEach ->
@@ -92,7 +92,7 @@
       it 'returns true when it unlocks the plugin successfully', ->
         sinon.stub(@plugin, 'Locked').returns false
         @communicator.plugin = @plugin
-        expect(@communicator.unlock()).to.be_true
+        expect(@communicator.unlock()).to.equal true

       xit 'throws an exception if the plugin fails to unlock'

@@ -122,22 +122,22 @@
       @isLockedStub = sinon.stub(@communicator, 'isLocked').returns true
       @unlockStub = sinon.stub(@communicator, 'unlock')
       @communicator.devices()
-      expect(@unlockStub.calledOnce).to.be_true
+      expect(@unlockStub.calledOnce).to.equal true

     it 'returns a promise', ->
       subject = @communicator.devices()
-      expect(subject.isDeferred).to.be_true
+      expect(subject.isDeferred).to.equal true

     it 'marks the communicator as being busy', ->
       @communicator.devices()
-      expect(@communicator.isBusy).to.be_true
+      expect(@communicator.isBusy).to.equal true

     it 'marks the communicator as being inactive once the promise is called', ->
       @FinishFindDevicesStub.returns false
       @communicator.devices()
-      expect(@communicator.isBusy).to.be_true
+      expect(@communicator.isBusy).to.equal true
       @FinishFindDevicesStub.returns true
-      expect(@communicator.isBusy).to.be_false
+      expect(@communicator.isBusy).to.equal false

     describe 'when the plugin is already busy', ->
       it 'does nothing', ->
@@ -153,11 +153,11 @@

       it 'will keeping checking until the communicator is finished loading every 100ms', ->
         @communicator.devices()
-        expect(@communicator._loopUntilFinishedFindingDevices.calledOnce).to.be_true
+        expect(@communicator._loopUntilFinishedFindingDevices.calledOnce).to.equal true
         @clock.tick(100)
-        expect(@communicator._loopUntilFinishedFindingDevices.calledTwice).to.be_true
+        expect(@communicator._loopUntilFinishedFindingDevices.calledTwice).to.equal true
         @clock.tick(100)
-        expect(@communicator._loopUntilFinishedFindingDevices.calledThrice).to.be_true
+        expect(@communicator._loopUntilFinishedFindingDevices.calledThrice).to.equal true

       describe 'xml', ->
         beforeEach ->


Updated API.

Updated API.

--- spec/fuzzy-finder-spec.coffee
+++ spec/fuzzy-finder-spec.coffee
@@ -923,8 +923,7 @@

       gitDirectory = atom.project.getDirectories()[1]
       gitRepository = atom.project.getRepositories()[1].async
-      waitsFor ->
-        not gitRepository._isRefreshing()
+      waitsForPromise -> gitRepository._refreshingPromise

     describe "git-status-finder behavior", ->
       [originalText, originalPath, newPath] = []
@@ -958,7 +957,7 @@

         runs ->
           expect(gitStatusView.find('.status.status-modified').length).toBe 1
-          expect(gitStatusView.find('.status.status-added').length).toBe 3
+          expect(gitStatusView.find('.status.status-added').length).toBe 1

     describe "status decorations", ->
       [originalText, originalPath, editor, newPath] = []


adding test for #26

adding test for #26

--- spec/sublime-tabs-spec.coffee
+++ spec/sublime-tabs-spec.coffee
@@ -113,3 +113,16 @@

         it 'replaces an existing temp tab', ->
           expect(tabBar.tabForItem(editor2)).not.toExist()
+
+      describe 'when opening an image', ->
+
+        it 'should be temporary', ->
+          imageView = null
+
+          waitsForPromise ->
+            atom.workspace.open('sample.png').then (o) ->
+              imageView = o
+              pane.activateItem(imageView)
+
+          runs ->
+            expect(tabBar.tabForItem(imageView)).toHaveClass 'temp'


webapp/kiosk iframe: cache single project table

--- src/smc-webapp/projects.cjsx
+++ src/smc-webapp/projects.cjsx
@@ -991,11 +991,18 @@
     load_recent_projects()

+_project_tables = {}
+
 load_single_project = (project_id) =>
     redux.getActions('page').setState({kiosk_project_id:project_id})
-    redux.removeTable('projects')
-    redux.createTable('projects', ProjectsTable)
-    await once(redux.getTable('projects')._table, "connected")
+    pt_cached = _project_tables[project_id]
+    if pt_cached
+        redux._tables[project_id] = pt_cached
+    else
+        redux.removeTable('projects')
+        pt = redux.createTable('projects', ProjectsTable)
+        _project_tables[project_id] = pt
+        await once(redux.getTable('projects')._table, "connected")

 ProjectsSearch = rclass