kernel: Use `jmp` for messaging

kernel: Use `jmp` for messaging

--- lib/kernel.coffee
+++ lib/kernel.coffee
@@ -73,9 +73,12 @@
          detail: detail, dismissable: /error/i.test method

     connect: ->
-        @shellSocket = zmq.socket 'dealer'
-        @controlSocket = zmq.socket 'dealer'
-        @ioSocket    = zmq.socket 'sub'
+        scheme = @config.signature_scheme.slice 'hmac-'.length
+        key = @config.key
+
+        @shellSocket = new jmp.Socket 'dealer', scheme, key
+        @controlSocket = new jmp.Socket 'dealer', scheme, key
+        @ioSocket    = new jmp.Socket 'sub', scheme, key

         @shellSocket.identity = 'dealer' + @language + process.pid
         @controlSocket.identity = 'control' + @language + process.pid
@@ -101,37 +104,6 @@
     interrupt: ->
         console.log "sending SIGINT"
         @kernelProcess.kill('SIGINT')
-
-    # send a signed Jupyter message (adapted from github.com/n-riesco/jmp)
-    signedSend: (message, socket) ->
-        encodedMessage =
-            idents:        message.idents or []
-            signature:     ''
-            header:        JSON.stringify message.header
-            parent_header: JSON.stringify (message.parent_header or {})
-            metadata:      JSON.stringify (message.metadata or {})
-            content:       JSON.stringify (message.content or {})
-
-        if (@config.key)
-            hmac = crypto.createHmac @config.signature_scheme.slice(5),
-                @config.key
-            toBuffer = (str) -> new Buffer str, "utf8"
-            hmac.update toBuffer encodedMessage.header
-            hmac.update toBuffer encodedMessage.parent_header
-            hmac.update toBuffer encodedMessage.metadata
-            hmac.update toBuffer encodedMessage.content
-            encodedMessage.signature = hmac.digest "hex"
-
-        console.log "signedMessage:", encodedMessage
-
-        socket.send encodedMessage.idents.concat [
-            '<IDS|MSG>'
-            encodedMessage.signature
-            encodedMessage.header
-            encodedMessage.parent_header
-            encodedMessage.metadata
-            encodedMessage.content
-        ]

     # onResults is a callback that may be called multiple times
     # as results come in from the kernel
@@ -158,7 +130,7 @@

         @executionCallbacks[requestId] = onResults

-        @signedSend message, @shellSocket
+        @shellSocket.send new jmp.Message message

     execute: (code, onResults) ->
         requestId = "execute_" + uuid.v4()
@@ -194,7 +166,7 @@

         @executionCallbacks[requestId] = onResults

-        @signedSend message, @shellSocket
+        @shellSocket.send new jmp.Message message

     inspect: (code, cursor_pos, onResults) ->
@@ -220,29 +192,30 @@

         @executionCallbacks[requestId] = onResults

-        @signedSend message, @shellSocket
+        @shellSocket.send new jmp.Message message

     addWatchCallback: (watchCallback) ->
         @watchCallbacks.push(watchCallback)

-    onShellMessage: (msgArray...) ->
-        message = @parseMessage msgArray
+    onShellMessage: (message) ->
+        message.type = message.header.msg_type
+
         console.log "shell message:", message

         if _.has(message, ['parent_header', 'msg_id'])
             callback = @executionCallbacks[message.parent_header.msg_id]
-        if callback? and _.has(message, ['contents', 'status'])
-
-            if message.contents.status == 'ok'
+        if callback? and _.has(message, ['content', 'status'])
+
+            if message.content.status == 'ok'
                 if message.type == 'complete_reply'
-                    matches = message.contents.matches
+                    matches = message.content.matches
                     # matches = _.map matches, (match) -> {text: match}
                     callback(matches)
                 else if message.type == 'inspect_reply'
                     callback {
-                        data: message.contents.data
-                        found: message.contents.found
+                        data: message.content.data
+                        found: message.content.found
                     }
                 else
                     callback {
@@ -251,10 +224,10 @@
                         stream: 'status'
                     }

-            else if message.contents.status == 'error'
-                errorString = message.contents.ename
-                if message.contents.evalue.length > 0
-                    errorString = errorString + "\n" + message.contents.evalue
+            else if message.content.status == 'error'
+                errorString = message.content.ename
+                if message.content.evalue.length > 0
+                    errorString = errorString + "\n" + message.content.evalue
                 callback {
                     data: errorString
                     type: 'text'
@@ -262,16 +235,17 @@
                 }

-    onIOMessage: (msgArray...) ->
-        message = @parseMessage msgArray
+    onIOMessage: (message) ->
+        message.type = message.header.msg_type
+
         console.log "IO message:", message

         if message.type == 'error'
             #TODO; produces to much warning & errors, maybe filter?
-            @stderr message.contents.evalue, message.contents.ename
+            @stderr message.content.evalue, message.content.ename

         if message.type == 'status'
-            status = message.contents.execution_state
+            status = message.content.execution_state
             @statusView.setStatus(status)

             if status == 'idle' and _.has(message, ['parent_header', 'msg_id'])
@@ -290,46 +264,46 @@
         if message.type == 'pyout' or
            message.type == 'display_data' or
            message.type == 'execute_result'
-            if message.contents.data['text/html']?
+            if message.content.data['text/html']?
                 return {
-                    data: message.contents.data['text/html']
+                    data: message.content.data['text/html']
                     type: 'text/html'
                     stream: 'pyout'
                 }
-            if message.contents.data['image/svg+xml']?
+            if message.content.data['image/svg+xml']?
                 return {
-                    data: message.contents.data['image/svg+xml']
+                    data: message.content.data['image/svg+xml']
                     type: 'image/svg+xml'
                     stream: 'pyout'
                 }

-            imageKeys = _.filter _.keys(message.contents.data), (key) ->
+            imageKeys = _.filter _.keys(message.content.data), (key) ->
                 return key.startsWith('image')
             imageKey = imageKeys[0]

             if imageKey?
                 return {
-                    data: message.contents.data[imageKey]
+                    data: message.content.data[imageKey]
                     type: imageKey
                     stream: 'pyout'
                 }
             else
                 return {
-                    data: message.contents.data['text/plain']
+                    data: message.content.data['text/plain']
                     type: 'text'
                     stream: 'pyout'
                 }
         else if message.type == 'stdout' or
                 message.prefix == 'stdout' or
                 message.prefix == 'stream.stdout' or
-                message.contents.name == 'stdout'
+                message.content.name == 'stdout'
             return {
-                data: message.contents.text ? message.contents.data
+                data: message.content.text ? message.content.data
                 type: 'text'
                 stream: 'stdout'
             }
         else if message.type == 'pyerr' or message.type == 'error'
-            stack = message.contents.traceback
+            stack = message.content.traceback
             stack = _.map stack, (item) -> item.trim()
             stack = stack.join('\n')
             return {
@@ -338,21 +312,6 @@
                 stream: 'error'
             }

-    parseMessage: (msg) ->
-        i = 0
-        while msg[i].toString('utf8') != '<IDS|MSG>'
-            i++
-
-        msgObject =
-            prefix: msg[0].toString('utf8')
-            header: JSON.parse msg[i+2].toString('utf8')
-            parent_header: JSON.parse msg[i+3].toString('utf8')
-            metadata: JSON.parse msg[i+4].toString('utf8')
-            contents: JSON.parse msg[i+5].toString('utf8')
-
-        msgObject.type = msgObject.header.msg_type
-        return msgObject
-
     destroy: ->
         console.log "sending shutdown"

@@ -372,7 +331,7 @@
                 header: header
                 content: content

-        @signedSend message, @shellSocket
+        @shellSocket.send new jmp.Message message

         @shellSocket.close()
         @ioSocket.close()


Hold off rendering until the promises resolve (#2878)

Hold off rendering the action button until the promises resolve

--- app/pages/home-logged-in.cjsx
+++ app/pages/home-logged-in.cjsx
@@ -106,8 +106,8 @@
               <ProjectIcon project={project} badge={badge} />

             </div>}
-        </div>}
-      <Link to={@state.href} className="call-to-action standard-button x-large"><Translate content={@state.action} /></Link>
+        </div>
+      <Link to={@state.href} className="call-to-action standard-button x-large"><Translate content={@state.action} /></Link>}
     </div>


Prepare IP for decaffeination

Prepare IP for decaffeination

--- src/lib/IP.coffee
+++ src/lib/IP.coffee
@@ -21,17 +21,18 @@
 # It is also possible to carry metadata with an IP object.
 # For example, the `datatype` and `schema` of the sending
 # port is transmitted with the IP object.
-module.exports = class IP
-  # Valid IP types
-  @types: [
+
+# Valid IP types
+validTypes = [
     'data'
     'openBracket'
     'closeBracket'
   ]

+module.exports = class IP
   # Detects if an arbitrary value is an IP
   @isIP: (obj) ->
-    obj and typeof obj is 'object' and obj._isIP is true
+    return obj and typeof obj is 'object' and obj._isIP is true

   # Creates as new IP object
   # Valid types: 'data', 'openBracket', 'closeBracket'
@@ -45,6 +46,7 @@
     @datatype = 'all'
     for key, val of options
       this[key] = val
+    return @

   # Creates a new IP copying its contents by value not reference
   clone: ->
@@ -56,12 +58,14 @@
         ip[key] = JSON.parse JSON.stringify val
       else
         ip[key] = val
-    ip
+    return ip

   # Moves an IP to a different owner
   move: (@owner) ->
     # no-op
+    return @

   # Frees IP contents
   drop: ->
     delete this[key] for key, val of @
+    return


Return proper Ember.RSVP.Promise to avoid troubles on browsers

--- app/routes/application.coffee
+++ app/routes/application.coffee
@@ -1,7 +1,7 @@
 Route = Ember.Route.extend
   model: ->
     # return the current user as the model if authenticated, otherwise a blank object
-    new Promise (resolve, reject) =>
+    new Ember.RSVP.Promise (resolve, reject) =>
       promise = $.ajax
         url: '/api/me'
         dataType: 'json'


.ttslua, active project

.ttslua, active project

Added .ttslua to file types (acts as lua)
Changed it so that it now counts active project, instead of first project.

--- lib/line-count.coffee
+++ lib/line-count.coffee
@@ -4,6 +4,7 @@
 sloc       = require 'sloc'
 filewalker = require 'filewalker'
 parser     = require 'gitignore-parser'
+path       = require 'path'

 suffixes = [
   "asm"
@@ -64,6 +65,7 @@
   "svg"
   "swift"
   "ts"
+  "ttslua"
   "vb"
   "vue"
   "xml"
@@ -118,9 +120,13 @@
         add pad(c.source, ws) + pad(c.comment, wc) + pad(c.total, wt) + '  ' + label
       null

+    rootDirPath = atom.project.getDirectories()[0].getPath()
+    editor = atom.workspace.getActiveTextEditor()
+    if editor and editor.getPath()
+      rootDirPath = path.dirname(editor.getPath())
+      console.log rootDirPath
+
     atom.workspace.open('line-count.txt').then (editor) =>
-      rootDirPath = atom.project.getDirectories()[0].getPath()
-
       files    = {}
       typeData = {}
       dirs     = {}
@@ -137,8 +143,14 @@

             code = fs.readFileSync absPath, 'utf8'
             code = code.replace /\r/g, ''
+
+            #hack to make sloc count ttslua as lua
+            type = sfx
+            if type == 'ttslua'
+              type = 'lua'
+
             try
-              counts = sloc code, sfx
+              counts = sloc code, type
             catch e
               add 'Warning: ' + e.message
               return


Fixed data caching, removed some debug output

--- knmi-weather.coffee
+++ knmi-weather.coffee
@@ -65,13 +65,14 @@
           )

     requestUpdate: () ->
+      @base.debug "#{@listenerCount 'weatherUpdate'} event listeners"
       if @__timeoutObject? and @lastWeatherData?
         @emit 'weatherUpdate', @lastWeatherData
       else
         @base.cancelUpdate()
         @base.debug "Requesting weather data update"
-        @getData().then (data) =>
-          @emit 'weatherUpdate', data
+        @getData().then (@lastWeatherData) =>
+          @emit 'weatherUpdate', @lastWeatherData
         .catch (error) =>
           @base.error "Error:", error
         .finally () =>
@@ -171,7 +172,7 @@

     createWeatherUpdateHandler: () ->
       return (weatherData) =>
-        @base.debug JSON.stringify weatherData.stations
+        # @base.debug JSON.stringify weatherData.stations
         stationData = (i for i in weatherData.stations when i.station is @config.station)[0]
         if stationData? and not _.isEmpty @config.attributes
           if @attributeHash.clouds? and _.isEmpty stationData.overcast?


Implementing 4 level AMF curve subdivision support

--- assets/js/p3d.coffee
+++ assets/js/p3d.coffee
@@ -273,10 +273,9 @@
       undefined

     # Scaling the object to mm
-    console.log xml
+    #console.log xml
     unitStr = root.getAttribute("unit") || root.getAttribute("units")
     scale = @_toMillimeters unitStr
-    console.log scale

     # initializing indice and vert counts
     vertCount = 0; indiceCount = 0
@@ -310,7 +309,6 @@
     # Adds a face's vertices and normals to the mesh at the given index
     addFace = (face, mesh, index) -> for attr in ['vertices', 'normals']
       mesh[attr][index+j*3+k] = face[attr][j][k] for j in [0..2] for k in [0..2]
-

     # Expanding (duplicating) the normals and verts so that there is a 1:1 of verts to indices
     # This allows us to modify the normals on a per-face basis in edges and makes mesh spliting trivial
@@ -327,10 +325,9 @@

     #return
     # Counting Curved Surfaces
-    subdivisionLevels = 1 # 4
+    subdivisionLevels = 4
     trianglesPerSurface = Math.pow 4, subdivisionLevels
-    @_eachFace (face) -> nOfTriangles += trianglesPerSurface if !isFlat face
-    console.log nOfTriangles*9
+    @_eachFace (face) -> ( nOfTriangles += trianglesPerSurface-1 if !isFlat face )

     # Subdividing Curved Surfaces
     exp[attr] = new Float32Array(nOfTriangles*9) for attr in ['vertices', 'normals']
@@ -348,9 +345,9 @@
           crossProduct = cross cross(n[i], d), n[i]
           ( magnitude(d) * crossProduct[j] / magnitude(crossProduct) for j in [0..2] )
         midVerts.push v01 = hermiteSpline(0.5, v, t, fIndex)
-        midNormals.push n01 = [0, 0, 0]# TODO: how to calculate the normal!?
-        #console.log "-----------------------" if fIndex < 2
-        # console.log n01 if fIndex < 2
+        # TODO: these normals are probably only aproximately correct.
+        # What is the proper way to calculate these??
+        midNormals.push n01 = ( (n[1][i] + n[0][i])/2 for i in [0..2] )

       newFaces = for i in [0..2] # calculating the outer subdivided faces
         vertices: [ midVerts[i], midVerts[ j = (i+2)%3 ], face.vertices[i] ]
@@ -359,11 +356,6 @@
       newFaces.push # calculating the center subdivided faces
         vertices: midVerts, normals: midNormals

-      # adding each subdivided face to the mesh
-      for f in newFaces
-        addFace f, exp, vertCount
-        #addFace face, exp, vertCount
-        vertCount += 9
       return newFaces

     # iterating through each original face and subdividing it if it is has non-uniform normals
@@ -374,16 +366,20 @@
         vertCount += 9
       else
         faces = [face]
+        newFaces = []
         for i in [0..subdivisionLevels-1]
           newFaces = []
           for f in faces
             newFaces.push f2 for f2 in subdivide f, fIndex + Math.pow(4,i)
           faces = newFaces
-
-    console.log @vertices.length
-    console.log vertCount
-    console.log indices.length
-    @indices = @indices = new Float32Array(nOfTriangles*3)
+        # adding each subdivided face to the mesh
+        for f in newFaces
+          addFace f, exp, vertCount
+          #addFace face, exp, vertCount
+          vertCount += 9
+
+
+    indices = @indices = new Float32Array(nOfTriangles*3)
     @indices[i] = i for i in [0..@indices.length-1]
     @[attr] = exp[attr] for attr in ['vertices', 'normals']
     @verts = @vertices
@@ -506,7 +502,7 @@

   # Splits the object into 2^16 vert chunks and returns the chunks
   split: () => # TODO: since each object's verts are 1:1 with indices at this point we can use monolithic subarrays here
-    console.log "splitting"
+    #console.log "splitting"
     bytesPerMesh = Math.pow(2,16) # is this even in bytes!?
     bytesPerMesh -= bytesPerMesh % 9 # Rounding the bytes per mesh down to the nearest face


vm.ejectCd() succeed even if the VBD cannot be destroyed.

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -274,7 +274,9 @@

   if cdDriveRef
     $wait xapi.call 'VBD.eject', cdDriveRef
-    $wait xapi.call 'VBD.destroy', cdDriveRef
+
+    # Silently attempts to destroy the VBD.
+    xapi.call('VBD.destroy', cdDriveRef).catch(->)

   return true


We don't even need to check repo.slug, repoSlug, should be always available

--- assets/scripts/app/models/job.coffee
+++ assets/scripts/app/models/job.coffee
@@ -31,8 +31,8 @@
   ).property()

   repoSlug: (->
-    @get('repositorySlug') || @get('repo.slug')
-  ).property('repo.slug', 'repositorySlug')
+    @get('repositorySlug')
+  ).property('repositorySlug')

   repoData: (->
     { id: @get('repoId'), slug: @get('repoSlug') }


fix panel expression querying

fix panel expression querying

--- lib/panel/view.coffee
+++ lib/panel/view.coffee
@@ -42,10 +42,12 @@
                     @inputBox.hide()
                 when OUTPUT
                     @show()
+                    @body.show()
                     @content.show()
                     @inputBox.hide()
                 when QUERY
                     @show()
+                    @body.show()
                     @content.hide()
                     @inputBox.show()
                 else
@@ -59,7 +61,7 @@
             @promise = Q.Promise (resolve, reject, notify) =>
                 # confirm
                 @on 'core:confirm', =>
-                    log 'Panel', "queried path: #{@inputBox.getText()}"
+                    log 'Panel', "queried string: #{@inputBox.getText()}"
                     @hide()
                     resolve @inputBox.getText()
         return @
@@ -108,7 +110,7 @@
     # placeholder of the inputbox of query mode
     setPlaceholder: (content) ->
         content = _.escape content
-        @inputBox.editor.placeholderText = content
+        @inputBox[0].getModel().placeholderText = content
         return @

     clearContent: ->


remove console.error

remove console.error

--- proxy.coffee
+++ proxy.coffee
@@ -114,8 +114,8 @@
                 console.log = (text) ->
                     logFile.write(text + '\n')

-                console.error = (text) ->
-                    logFile.write(text + '\n')
+                ###console.error = (text) ->
+                    logFile.write(text + '\n')###

     setControllers: ->
         @app.get "/routes", @showRoutesAction


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/number_extensions.coffee
+++ test/number_extensions.coffee
@@ -22,6 +22,7 @@
   equals (-5).clamp(0, 1), 0
   equals 1.clamp(0, null), 1
   equals (-1).clamp(0, null), 0
+  equals (-10).clamp(-5, 0), -5
   equals (-10).clamp(-5, 0), -5

 test "#floor", ->


chmod -x proxy.coffee

chmod -x proxy.coffee


quick fix for autocompletion

quick fix for autocompletion

--- proxy.coffee
+++ proxy.coffee
@@ -124,7 +124,7 @@
     socket.on 'RestartThreads', ({win}) -> cmd 'DebugRestartThreads', "<win>#{win}</win>"
     socket.on 'Cutback',        ({win}) -> cmd 'DebugCutback',        "<win>#{win}</win>"
     socket.on 'WeakInterrupt', -> cmd 'WeakInterrupt'
-    socket.on 'GetAutoComplete', ({line, pos, token}) -> cmd 'GetAutoComplete', "<line>#{b64 line}</line><pos>#{pos}</pos><token>#{token}</token>"
+    socket.on 'Autocomplete', ({line, pos, token}) -> cmd 'GetAutoComplete', "<line>#{b64 line}</line><pos>#{pos}</pos><token>#{token}</token>"

     socket.on 'connectToInterpreter', ({host, port}) -> connectToInterpreter host, port


Add consistent string treatments.

Add consistent string treatments.

--- octokit.coffee
+++ octokit.coffee
@@ -753,9 +753,9 @@
           @createBranch = (newBranchName) ->
             _getRef()
             .then (branch) =>
-              _git.getSha(branch, "")
+              _git.getSha(branch, '')
               .then (sha) =>
-                _git.createRef({sha:sha, ref:"refs/heads/" + newBranchName})
+                _git.createRef({sha:sha, ref:"refs/heads/#{newBranchName}"})

             # Return the promise
             .promise()


Register some Italian translations for site text

--- app/pages/project/project-page.cjsx
+++ app/pages/project/project-page.cjsx
@@ -18,6 +18,20 @@
       classify: 'Classify'
       talk: 'Talk'
       collections: 'Collect'
+
+counterpart.registerTranslations 'it',
+{
+  project: {
+    disclaimer: "Questo progetto e' stato creato con il Project Builder di Zooniverse, ma non e' ancora un progetto ufficiale. Per questo motivo e' possibile che domande su questo progetto dirette al Team Zooniverse non ricevano una risposta.",
+    loading: "Caricamento progetto",
+    nav: {
+      about: "A proposito",
+      classify: "Classifica",
+      talk: "Forum",
+      collections: "Colleziona"
+    }
+  }
+}

 AVATAR_SIZE = 100


code cleaning

code cleaning

--- proxy.coffee
+++ proxy.coffee
@@ -307,7 +307,7 @@
             helpers.sendResetEmail instance, user, key, (err, result) =>
                 if err
                     console.log err
-                    @sendErrorr res, "Email cannot be sent"
+                    @sendError res, "Email cannot be sent"
                 else
                     @sendSuccess res, "Reset email sent."


Thread commenting

Thread commenting

--- app/scripts/controllers/chat.coffee
+++ app/scripts/controllers/chat.coffee
@@ -1,4 +1,4 @@
-angular.module("kvetchApp").controller "ChatCtrl", ($scope, $firebase, $timeout) ->
+angular.module("kvetchApp").controller "ChatCtrl", ($scope, $firebase, $timeout, FocusManager) ->
   alert = (msg) ->
     $scope.err = msg

@@ -6,13 +6,16 @@
       $scope.err = null
     , 5000

-  $scope.rootId = parentId = '-JeET71Lq_8X116CKmTs'
+  $scope.rootId = '-JeET71Lq_8X116CKmTs'
   messagesRef = new Firebase('https://kvetch.firebaseio.com/messages/')

   $scope.newMessage = {}

   $scope.addMessage = ->
     return unless $scope.newMessage.text
+
+    parent = FocusManager.focused
+    parentId = parent.attr('rootId')

     $scope.newMessage.createdAt = +new Date
     $scope.newMessage.parents = [parentId]


removed unneeded var

removed unneeded var

--- oridomi.coffee
+++ oridomi.coffee
@@ -1192,8 +1192,7 @@
   map: (fn) ->
     prep((angle, anchor, options) =>
       @_iterate anchor, (panel, i, len) =>
-        deg = fn angle, i, len
-        @_transformPanel panel, deg, anchor, options.fracture
+        @_transformPanel panel, fn(angle, i, len), anchor, options.fracture
     ).bind @


moving methods

moving methods

--- oridomi.coffee
+++ oridomi.coffee
@@ -966,6 +966,16 @@
   # ==============

+  # Enables touch events.
+  enableTouch: ->
+    @_setTouch true
+
+
+  # Disables touch events.
+  disableTouch: ->
+    @_setTouch false
+
+
   # Public setter for transition durations.
   setSpeed: (speed) ->
     @_setTrans (@_settings.speed = speed), @_settings.ripple
@@ -1027,16 +1037,6 @@
     @

-  # Enables touch events.
-  enableTouch: ->
-    @_setTouch true
-
-
-  # Disables touch events.
-  disableTouch: ->
-    @_setTouch false
-
-
   # Enable or disable ripple. 1 is forwards, 2 is backwards, 0 is disabled.
   setRipple: (dir = 1) ->
     @_settings.ripple = Number dir


fix(db): Disable verbose warnings from DatabaseView

--- src/flux/stores/database-view.coffee
+++ src/flux/stores/database-view.coffee
@@ -4,7 +4,7 @@
 ModelView = require './model-view'
 EventEmitter = require('events').EventEmitter

-verbose = true
+verbose = false

 # A small helper class that prevents the DatabaseView from making too many
 # queries. It tracks the number of jobs in flight via `increment` and allows


String#extension

String#extension

--- test/string_extensions.coffee
+++ test/string_extensions.coffee
@@ -21,7 +21,7 @@

   equals "jquery.min.js".extension(), "js"

-  equals ""
+  equals "src/bouse."

 test "#humanize", ->
   equals "employee_salary".humanize(), "Employee salary"


fix(composer): fix bluring in contenteditable

--- internal_packages/composer/lib/contenteditable-component.cjsx
+++ internal_packages/composer/lib/contenteditable-component.cjsx
@@ -448,13 +448,11 @@
     else return false

   _onBlur: (event) =>
-    # console.log "On Blur Contenteditable"
     @setInnerState dragging: false
     # The delay here is necessary to see if the blur was caused by us
     # navigating to the toolbar and focusing on the set-url input.
-    _.delay =>
-      @setInnerState editableFocused: false
-    , 50
+    return if @_editableNode().parentElement.contains event.relatedTarget
+    @setInnerState editableFocused: false

   _onFocus: (event) =>
     @setInnerState editableFocused: true


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera.coffee
+++ src/camera.coffee
@@ -83,8 +83,6 @@
     transformFilterChain: (fn) ->
       transformFilters.push fn

-  self.include(Bindable)
-
   self.attrAccessor "transform"

   self.bind "afterUpdate", ->


Ignored Err Code3

Ignored Err Code3

--- help/troubleshoot/apache.coffee
+++ help/troubleshoot/apache.coffee
@@ -16,19 +16,27 @@

   # Ask the apache2 service for a status.
   exec 'service apache2 status', (err, stdout, stderr) ->
-    if err?
+    # Failures from "service" appear to return error codes, but we're ignoring
+    # code #3 since we're matching the string manually below. In the future,
+    # if we want to match multiple failure types we should handle this
+    # properly.
+    if err? and err.code isnt 3
       out += """
       There was an error communicating with Apache. This in itself may be a
       problem. The error message is below:

-        #{err.message}
+        Code:     #{err.code}
+        Message:  #{err.message}
       """
       return done()

     # Check if apache is not running
     if stdout is "Apache2 is NOT running.\n"
       out += """
-      Apache does not appear to be running
+      Apache does not appear to be running. To start it, try running the
+      following command:
+
+        sudo service apache2 start
       """
     else if stdout[0...18] isnt "Apache2 is running"
       out += """
@@ -37,3 +45,4 @@
       """

     done()
+


update js bundle file name

update js bundle file name

--- app/templates/gulp/tasks/rev.coffee
+++ app/templates/gulp/tasks/rev.coffee
@@ -28,7 +28,7 @@
 #     $REV JS
 #*------------------------------------*/
 gulp.task 'rev:js', ["minify:js"], () ->
-  gulp.src(["#{conf.path.prod.js}/built.min.js"])
+  gulp.src(["#{conf.path.prod.js}/main.bundle.min.js"])
     .pipe rev()
     .pipe gulp.dest(conf.path.prod.js)
     .pipe rev.manifest(conf.revManifest.path, conf.revManifest.opts)


Use Google API key in the configuration

Use Google API key in the configuration

--- client/landing/site.landing/coffee/about/AppView.coffee
+++ client/landing/site.landing/coffee/about/AppView.coffee
@@ -59,7 +59,7 @@

Where we are?

       <address>358 Brannan Street, San Francisco, CA 94107</address>

-
+


Landing: Login: remove gitlab related views from solo login

--- client/landing/site.landing/coffee/login/AppView.coffee
+++ client/landing/site.landing/coffee/login/AppView.coffee
@@ -83,11 +83,6 @@
       partial  : '...'

     { oauthController } = kd.singletons
-
-    @gitlabIcon = new kd.CustomHTMLView
-      tagName   : 'span'
-      cssClass  : 'gl icon'
-      click     : -> oauthController.redirectToOauth { provider: 'gitlab' }

     @githubIcon = new kd.CustomHTMLView
       tagName   : 'span'
@@ -163,7 +158,6 @@

-          {{> @gitlabIcon}}
           {{> @githubIcon}}
           {{> @gplusIcon}}
           {{> @facebookIcon}}


some refactoring

some refactoring

--- src/math3d.coffee
+++ src/math3d.coffee
@@ -212,7 +212,7 @@
             if error
                 return @opts.callback? error

-            @attach_to_dom @opts.parent
+            @attachToDom @opts.parent

             if @_init
                 return @opts.callback? undefined, @
@@ -237,14 +237,16 @@
             # initialize the scene
             @scene = new THREE.Scene()

+            # functions in change hooks will be run when the controls
+            # recieve a change event
+            @changeHooks = []
+
             # IMPORTANT: There is a major bug in three.js -- if you make the width below more than .5 of the window
             # width, then after 8 3d renders, things get foobared in WebGL mode.  This happens even with the simplest
             # demo using the basic cube example from their site with R68.  It even sometimes happens with this workaround, but
             # at least retrying a few times can fix it.
-            if not @opts.width?
-                @opts.width  = document.documentElement["clientWidth"]*.5
-
-            @opts.height = if @opts.height? then @opts.height else @opts.width*2/3
+            @opts.width ?= document.documentElement["clientWidth"]/2
+            @opts.height ?= @opts.width*2/3

             @setDynamicRenderer()
             @init_orbit_controls()
@@ -255,14 +257,14 @@

             @opts.callback? undefined, @

-    attach_to_dom: (parent_element) ->
+    attachToDom: (parentElement) ->
         if @element?
             removeElement @element
         else
             @element = document.createElement 'span'
             @element.className = 'math-3d-viewer'

-        parent_element.appendChild @element
+        parentElement.appendChild @element

     # client code should call this when done adding objects to the scene
     finalize: ->
@@ -374,17 +376,8 @@
         up = new THREE.Vector3()
         @controls.addEventListener 'change', =>
             if @renderer_type is 'dynamic'
-
-                if @_points?
-                    for point in @_points
-                        scale = @camera.position.distanceTo point.position
-                        point.scale.set scale, scale, scale
-
-                if @_text?
-                    up.set(0, 1, 0).applyQuaternion @camera.quaternion
-                    for mesh in @_text
-                        mesh.up = up.clone()
-                        mesh.lookAt @camera.position
+                for hook in @changeHooks
+                    hook()

                 @renderer.render @scene, @camera

@@ -451,13 +444,22 @@
             texture     : required
             in_frame    : true

+        if not (opts.rotation? or @_text?)
+            @_text = []
+
+            up = new THREE.Vector3()
+            @changeHooks.push =>
+                up.set(0, 1, 0).applyQuaternion @camera.quaternion
+                for text in @_text
+                    text.up.copy up
+                    text.lookAt @camera.position
+
         opts.depth = 0
         text = @addText3d opts
+
         if not opts.rotation?
-            if @_text?
-                @_text.push text
-            else
-                @_text = [text]
+            @_text.push text
+
         return text

     addText3d: (opts) ->
@@ -483,7 +485,7 @@
         material.color.setRGB opts.texture.color...

         text = new THREE.Mesh geometry, material
-        @rescale text.position.set(opts.loc...)
+        text.position.set opts.loc...
         text.rotation.set opts.rotation...

         geometry.computeBoundingBox()
@@ -503,6 +505,8 @@
             @matrixWorldNeedsUpdate = true

         @_finalizeObj text, opts.in_frame
+
+        @rescale text.position
         text.scale.copy @squareScale
         return text

@@ -536,23 +540,75 @@
         if opts._basic_material
             material = new THREE.MeshBasicMaterial
                 transparent : opts.texture.opacity < 1
-
-            material.color.setRGB       opts.texture.color...
-
         else
             material = new THREE.MeshPhongMaterial
                 transparent : opts.texture.opacity < 1
                 side        : THREE.DoubleSide

-            material.color.setRGB       opts.texture.color...
             material.ambient.setRGB     opts.texture.ambient...
             material.specular.setRGB    opts.texture.specular...
-            material.opacity          = opts.texture.opacity
+        material.color.setRGB       opts.texture.color...
+        material.opacity          = opts.texture.opacity

         sphere = new THREE.Mesh geometry, material
-        @rescale sphere.position.set(opts.loc...)
-
-        return @_finalizeObj sphere, opts.in_frame
+        sphere.position.set opts.loc...
+
+        @_finalizeObj sphere, opts.in_frame
+        @rescale sphere.position
+        return sphere
+
+    _addCloudPoint: (opts) ->
+        if not @_cloud
+            @_cloud = {}
+
+        key = opts.size+opts.texture.color+opts.in_frame
+
+        if not (cloud = @_cloud[key])?
+            material = new THREE.PointCloudMaterial
+                size            : opts.size*2
+                sizeAttenuation : false
+            material.color.setRGB opts.texture.color...
+            cloud = @_cloud[key] = new THREE.PointCloud(
+                                        new THREE.Geometry(), material)
+            cloud.scale.copy @aspectRatio
+            @scene.add cloud
+
+        cloud.geometry.vertices.push new THREE.Vector3(opts.loc...)
+
+        if opts.in_frame
+            @updateBoundingBox cloud
+        return cloud
+
+    _initPointHelper: ->
+        if not @_pointHelper?
+            @_pointHelperVec = new THREE.Vector3()
+
+            @_pointHelper = new THREE.Mesh()
+            @_pointHelper.geometry.vertices.push @_pointHelperVec
+
+    _addSpherePoint: (opts) ->
+        if not @_points?
+            @_points = []
+            @changeHooks.push =>
+                for point in @_points
+                    scale = @camera.position.distanceTo point.position
+                    point.scale.set scale, scale, scale
+
+        if opts.in_frame
+            @_initPointHelper()
+            @_pointHelperVec.set(opts.loc...)
+            @updateBoundingBox @_pointHelper
+
+        opts.radius = opts.size/1200
+        opts._basic_material = true
+        opts.in_frame = false
+
+        delete opts.size
+        delete opts.use_cloud
+
+        point = @addSphere opts
+        @_points.push point
+        return point

     addPoint: (opts) ->
         opts = defaults opts,
@@ -563,52 +619,9 @@
             in_frame    : true

         if opts.use_cloud
-            if not @_cloud
-                @_cloud = {}
-
-            key = opts.size+opts.texture.color+opts.in_frame
-
-            if not (cloud = @_cloud[key])?
-                material = new THREE.PointCloudMaterial
-                    size            : opts.size*2
-                    sizeAttenuation : false
-                material.color.setRGB opts.texture.color...
-                cloud = @_cloud[key] = new THREE.PointCloud(
-                                            new THREE.Geometry(), material)
-                cloud.scale.copy @aspectRatio
-                @scene.add cloud
-
-            cloud.geometry.vertices.push new THREE.Vector3(opts.loc...)
-
-            if opts.in_frame
-                @updateBoundingBox cloud
-            return cloud
+            return @_addCloudPoint opts
         else
-            if not @_points?
-                @_points = []
-
-            in_frame = opts.in_frame
-            loc = opts.loc
-
-            opts.radius = opts.size/1200
-            opts._basic_material = true
-            opts.in_frame = false
-
-            delete opts.size
-            delete opts.use_cloud
-
-            point = @addSphere opts
-
-            @_points.push point
-
-            if in_frame
-                if not @_pointHelper?
-                    @_pointHelper = new THREE.Mesh()
-                    @_pointHelper.geometry.vertices.push new THREE.Vector3()
-
-                @_pointHelper.geometry.vertices[0].set loc...
-                @updateBoundingBox @_pointHelper
-            return point
+            return @_addSpherePoint opts

     addIndexFaceSet: (opts) ->
         opts = defaults opts,


#copy

#copy

--- src/matrix.coffee
+++ src/matrix.coffee
@@ -109,6 +109,15 @@
       )

     ###*
+    Copy this matrix.
+    @name copy
+    @methodOf Matrix#
+    @returns {Matrix} A copy of this matrix.
+    ###
+    copy: ->
+      Matrix(@a, @b, @c, @d, @tx, @ty)
+
+    ###*
     Given a point in the pretransform coordinate space, returns the coordinates of
     that point after the transformation occurs. Unlike the standard transformation
     applied using the transformPoint() method, the deltaTransformPoint() method


Increase rollout to 3%

Increase rollout to 3%

--- services/web/app/coffee/Features/Project/ProjectController.coffee
+++ services/web/app/coffee/Features/Project/ProjectController.coffee
@@ -257,7 +257,7 @@
 				timestamp = parseInt(user_id.toString().substring(0, 8), 16)
 				counter = parseInt(user_id.toString().substring(18, 24), 16)

-				rolloutPercentage = 1 # Percentage of users to roll out to
+				rolloutPercentage = 3 # Percentage of users to roll out to
 				if counter % 100 > rolloutPercentage
 					# Don't show if user is not part of roll out
 					return cb(null, false)


sort disableds to the bottom of each section

--- lib/installed-packages-panel.coffee
+++ lib/installed-packages-panel.coffee
@@ -80,6 +80,18 @@
   addPackageViews: (container, packages) ->
     container.empty()
     packageViews = []
+
+    packages.sort (left, right) ->
+      leftStatus = atom.packages.isPackageDisabled(left.name)
+      rightStatus = atom.packages.isPackageDisabled(right.name)
+      if leftStatus == rightStatus
+        return 0
+      else if leftStatus > rightStatus
+        return 1
+      else
+        return -1
+
+
     for pack, index in packages
       packageRow = $$ -> @div class: 'row'
       container.append(packageRow)


Removed deprecated file

Removed deprecated file

--- app/assets/text_file_input.coffee
+++ app/assets/text_file_input.coffee
@@ -1,23 +0,0 @@
-chooseFileText = "Choose a file"
-
-$(document).ready ->
-  $('input[type="file"]').each initializeFileInput
-
-initializeFileInput = () ->
-  realFileInput = $(this)
-  label = $('
'+chooseFileText+'
')
-  invisibleWrapper = $('

').css({height:0,width:0,'overflow':'hidden'})
-  customFileInputWrapper = $('

')
-
-  realFileInput.wrap(customFileInputWrapper).after(label).wrap(invisibleWrapper)
-
-  realFileInput.change () ->
-    fileNameList = $(this).parent().parent().find('.file-name')
-    if fileNameList.length > 0
-      fileNameList.first().text($(this).val())
-    else
-      fileName = $('
'+$(this).val()+'
')
-      label.after(fileName)
-
-  label.click () ->
-    label.prev().children().first().click()


add defaults for theme options

add defaults for theme options

fixes #42

--- src/javascripts/configuration.coffee
+++ src/javascripts/configuration.coffee
@@ -111,6 +111,11 @@
     # Using a <iframe> tag is considered the default
     iframeMode: 'iframe'
     locale: 'en-US'
+    theme: 'default'
+    themeHtml: null
+    themeCss: null
+    customStyle: null
+    startPanel: null
   }

   configureTemplating: =>


Push top-level require() calls down to function level

--- lib/composer.coffee
+++ lib/composer.coffee
@@ -1,18 +1,18 @@
 _ = require 'underscore-plus'
 fs = require 'fs-plus'
 path = require 'path'
+ConsoleLogger = null
 ConfigSchema = require './config-schema'
-ConsoleLogger = require './loggers/console-logger'
 LatexmkBuilder = require './builders/latexmk'
-MasterTexFinder = require './master-tex-finder'
+MasterTexFinder = null

-ErrorIndicatorView = require './error-indicator-view'
-ProgressIndicatorView = require './progress-indicator-view'
+ErrorIndicatorView = null
+ProgressIndicatorView = null

 module.exports =
 class Composer
   constructor: (logger)->
-    @log = logger ? new ConsoleLogger()
+    @log = logger ? @getDefaultLogger()

   build: ->
     editor = atom.workspace.getActivePaneItem()
@@ -109,6 +109,10 @@
         return
     return new OpenerImpl()

+  getDefaultLogger: ->
+    ConsoleLogger ?= require './loggers/console-logger'
+    new ConsoleLogger()
+
   moveResult: (result, filePath) ->
     originalFilePath = result.outputFilePath
     result.outputFilePath = @alterParentPath(filePath, result.outputFilePath)
@@ -120,6 +124,7 @@
       fs.moveSync(syncFilePath, @alterParentPath(filePath, syncFilePath))

   resolveRootFilePath: (filePath) ->
+    MasterTexFinder ?= require './master-tex-finder'
     finder = new MasterTexFinder(filePath)
     finder.getMasterTexPath()

@@ -150,6 +155,7 @@
   showProgressIndicator: ->
     return @indicator if @indicator?

+    ProgressIndicatorView ?= require './progress-indicator-view'
     @indicator = new ProgressIndicatorView()
     @statusBar?.addRightTile({item: @indicator, priority: 9001})
     @indicator
@@ -157,6 +163,7 @@
   showErrorIndicator: ->
     return @errorIndicator if @errorIndicator?

+    ErrorIndicatorView ?= require './error-indicator-view'
     @errorIndicator = new ErrorIndicatorView()
     @statusBar?.addRightTile({item: @errorIndicator, priority: 9001})
     @errorIndicator


Add temporary password protection

Add temporary password protection


Don't freak out if the runner's not found

--- lib/redeye.coffee
+++ lib/redeye.coffee
@@ -73,7 +73,7 @@
         @workers[str] = new Worker(str, this, @sticky)
         @workers[str].run()
       catch e
-        @error e
+        @error e unless e == 'no_runner'
       @emit 'next'

   # Shut down the redis connection and stop running workers
@@ -110,7 +110,8 @@
     @resp_channel = _('responses').namespace @queue.options.db_index
     unless @runner = @queue.runners[@prefix]
       @emit @key, null
-      throw new Error("no runner for '#{@prefix}' (#{@key})")
+      console.log "no runner for '#{@prefix}' (#{@key})"
+      throw 'no_runner'
     @cache = {}
     @saved_keys = {}
     @last_stage = 0


Switching to K2 group

Switching to K2 group

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -21,9 +21,10 @@

 $ = window.jQuery

-K1_SUBJECT_GROUP  =  "5417014a3ae7400bda000001"
+K1_SUBJECT_GROUP   = "5417014a3ae7400bda000001"
 K2_0_SUBJECT_GROUP = "547d05ce415ac13139000001"
-MAIN_SUBJECT_GROUP = K1_SUBJECT_GROUP
+K2_1_SUBJECT_GROUP = "54f4c5ab8f165b6e85000001"
+MAIN_SUBJECT_GROUP = K2_1_SUBJECT_GROUP
 SIMULATION_GROUP   = "5417014b3ae7400bda000002"

 USERS_OPT_IN = ['a', 'b', 'c', 'g', 'h', 'i']


host includes port, hostname does not.

host includes port, hostname does not.

--- lib/router.coffee
+++ lib/router.coffee
@@ -47,12 +47,12 @@

   else if target.protocol == 'http:'
     proxy.proxyRequest req, res,
-      host: target.host
+      host: target.hostname
       port: target.port || 80

   else if target.protocol == 'https:'
     proxy.proxyRequest req, res,
-      host: target.host
+      host: target.hostname
       port: target.port || 443
       target:
         https: true
@@ -60,7 +60,7 @@
   else if target.protocol == 'redirect:'
     parsed = url.parse req.url
     parsed.protocol = 'http:'
-    parsed.host = target.host
+    parsed.host = target.hostname
     parsed.port = target.port
     target.pathname = path.join target.pathname, parsed.pathname


Use API endpoint var as heartbeat endpoint.

--- src/config.coffee
+++ src/config.coffee
@@ -9,8 +9,9 @@
 	localImage: process.env.SUPERVISOR_IMAGE ? 'resin/rpi-supervisor'
 	configMountPoint: process.env.CONFIG_MOUNT_POINT ? '/mnt/mmcblk0p1/config.json'
 	ledFile: process.env.LED_FILE ? '/sys/class/leds/led0/brightness'
-	heartbeatEndpoint: process.env.HEARTBEAT_ENDPOINT ? 'http://image.resin.io/ping'
 	successMessage: 'SUPERVISOR OK'
+
+config.heartbeatEndpoint = config.apiEndpoint + '/ping'

 config.remoteImage = config.registryEndpoint + '/' + config.localImage


Show linters errors from stderr

Show linters errors from stderr

--- lib/linter-pylama.coffee
+++ lib/linter-pylama.coffee
@@ -157,15 +157,15 @@
       args: args
       options:
         env: env
-        stream: 'stdout'
-        ignoreExitCode: true
-        throwOnStdErr: false
+        stream: 'both'

   lintFile: (lintInfo, textEditor) ->
-    helpers.exec(lintInfo.command, lintInfo.args, lintInfo.options).then (output) ->
-      console.log output if do atom.inDevMode
-      helpers.parse(output, regex).map (message) ->
+    helpers.exec(lintInfo.command, lintInfo.args, lintInfo.options).then (output) =>
+      if output['stderr']
+        atom.notifications.addWarning output['stderr']
+      console.log output['stdout'] if do atom.inDevMode
+      helpers.parse(output['stdout'], regex).map (message) ->
         code = "#{message.type}#{message.filePath}"
         message.type = if message.type in ['E', 'F']
           'Error'


Add TODO comment

Add TODO comment

--- src/config.coffee
+++ src/config.coffee
@@ -1158,6 +1158,7 @@
     resultObject = object
   resultObject

+# TODO remove in 1.0 API
 Config::unobserve = (keyPath) ->
   Grim.deprecate 'Config::unobserve no longer does anything. Call `.dispose()` on the object returned by Config::observe instead.'


Add snippet to create snippets under a source definition

--- lib/snippets.cson
+++ lib/snippets.cson
@@ -5,11 +5,20 @@
       {
         "${1:.source.js}": {
           "${2:Snippet Name}": {
-            "prefix": "${3:hello}",
+            "prefix": "${3:Snippet Trigger}",
             "body": "${4:Hello World!}"
           }
         }
       }$5
+    """
+
+  'Atom Snippet Keys':
+    prefix: 'snipk'
+    body: """
+      "${1:Snippet Name}": {
+        "prefix": "${2:Snippet Trigger}",
+        "body": "${3:Hello World!}"
+      }$4
     """

   'Atom Keymap':
@@ -28,8 +37,16 @@
     body: """
       '${1:.source.js}':
         '${2:Snippet Name}':
-          'prefix': '${3:hello}'
+          'prefix': '${3:Snippet Trigger}'
           'body': '${4:Hello World!}'$5
+    """
+
+  'Atom Snippet Keys':
+    prefix: 'snipk'
+    body: """
+      '${1:Snippet Name}':
+        'prefix': '${2:Snippet Trigger}'
+        'body': '${3:Hello World!}'$5
     """

   'Atom Keymap':


:abc: Remove Extraneous Logging

:abc: Remove Extraneous Logging

--- lib/executor.coffee
+++ lib/executor.coffee
@@ -18,10 +18,6 @@
     stdout = (data) -> output += data
     stderr = (data) -> error += data
     exit = (data) ->
-      console.log output
-      console.log error
-      console.log data
-      console.log error.replace(/\r?\n|\r/g, '') is "\'\"" + command + "\"\' is not recognized as an internal or external command,operable program or batch file."
       if error? and error isnt '' and error.replace(/\r?\n|\r/g, '') is "\'\"" + command + "\"\' is not recognized as an internal or external command,operable program or batch file."
         message =
             line: false
@@ -35,9 +31,6 @@
       code = data
       callback(code, output, error, messages)
     args = [] unless args?
-    console.log command
-    console.log args
-    console.log options
     bufferedprocess = new BufferedProcess({command, args, options, stdout, stderr, exit})
     bufferedprocess.process.on 'error', (err) =>
       if err.code is 'ENOENT'
@@ -48,7 +41,7 @@
             type: 'error'
             source: 'executor'
         messages.push message
-      #else
-      console.log err
+      else
+        console.log err

       callback(127, output, error, messages)


fix #9

fix #9

--- app/js/controllers/account.coffee
+++ app/js/controllers/account.coffee
@@ -68,7 +68,7 @@
             update_delegate_info (acct) # update delegate info
             Blockchain.get_asset(0).then (asset_type) ->
                 $scope.account.delegate_info.pay_balance_asset = Utils.asset($scope.account.delegate_info.pay_balance, asset_type)
-            $state.go "account.delegate"
+            # $state.go "account.delegate"
             $scope.del_tab = true # necessary to display tab html

         #check if already registered.  this call should be removed when the name conflict info is added to the Wallet.get_account return value


Simplification of event emitter construction

--- app/lib/sdk/request_action.coffee
+++ app/lib/sdk/request_action.coffee
@@ -58,8 +58,7 @@

   invoke: (options) ->
     start = Date.now()
-    events = new EventEmitter
-    events.names = "ready"
+    events = new EventEmitter "ready"
     schedule = @constructor.schedule()
     command = @constructor.command()
     result = @constructor.result()


feat(credentials): add method to load credentials from env

env variables:
- CTP_PROJECT_KEY
- CTP_CLIENT_ID
- CTL_CLIENT_SECRET

--- src/coffee/helpers/project-credentials-config.coffee
+++ src/coffee/helpers/project-credentials-config.coffee
@@ -62,6 +62,8 @@
               @_readCsvConfig "#{contents}"
         else Promise.resolve {}

+    configsP.push(@_getEnvCredetials())
+
     Promise.all(configsP)
     .then (configs) ->
       _.reduce configs.reverse(), ((acc, c) -> _.extend(acc, c)), {}
@@ -84,6 +86,16 @@
           acc[json.project_key] = json; acc
         , {}
       .on 'error', (error) -> reject error
+
+  _getEnvCredetials: ->
+    projectKey = process.env.CTP_PROJECT_KEY
+    if projectKey
+      return {
+        "#{projectKey}":
+          project_key: process.env.CTP_PROJECT_KEY
+          client_id: process.env.CTP_CLIENT_ID
+          client_secret: process.env.CTP_CLIENT_SECRET
+      }

   ###*
    * Returns project credentials for the project key.


fix test

fix test

--- spec/IntegrationSpec.coffee
+++ spec/IntegrationSpec.coffee
@@ -18,4 +18,4 @@
   it 'loads site.js from another server', ->
     expect($widget).toExist()
     expect($widget.data("shopid")).toEqual("DemoShop")
-    expect($widget.text()).toEqual("Loading ...")
+    expect($widget.text()).toContainText("Loading")


fix spec

fix spec

--- spec/IntegrationSpec.coffee
+++ spec/IntegrationSpec.coffee
@@ -14,7 +14,7 @@
     @$el.find(selector)

   hasCategoryList: =>
-    @$("option").length > 0
+    @$(".epages-shop-category-list select option").length > 0

   hasProductList: =>
     @$(".epages-shop-product").length > 0
@@ -23,7 +23,7 @@
     @$(".epages-shop-search-form form input").length > 0

   hasSorting: =>
-    @$(".epages-shop-sort a").length > 0
+    @$(".epages-shop-sort select option").length > 0

   isReady: =>
     @hasCategoryList() && @hasProductList()


removed state, only take in props

removed state, only take in props

--- imports/ui/pages/NewInstancePage.cjsx
+++ imports/ui/pages/NewInstancePage.cjsx
@@ -1,37 +1,47 @@
 React = require 'react'
 Page  = require '../Page.cjsx'
 _     = require 'lodash'
-{ Box, Heading, Header, Form, FormFields, FormField, TextInput, Select }     = require 'grommet'
+{ Box, Heading, Header, Form, FormFields, FormField, TextInput, Select, CheckBox }     = require 'grommet'

 module.exports  = React.createClass
   displayName: 'NewInstancePage'
+
+  getAppParams: ->
+    params = @props.selectedApp.dockerCompose?.match /(?:\{\{)([\d|\w|_|-]*?)(?=\}\})/g
+    console.log params
+    if params?.length
+      _.uniq(params.map (p) -> p.replace('{{', '').trim())
+    else
+      []

   getAppNameFromProps: ->
     if name = @props.selectedAppName and version = @props.selectedAppVersion
       "#{name} (#{version})"

-  getInitialState: ->
-    app: @getAppNameFromProps() or ''
-    name: ''
+  onAppSelectChange: (evt) ->
+    @props.onAppSelected evt.value.name, evt.value.version

-  onAppSelectChange: (evt) ->
-    console.log evt
-    @setState app: evt.value
+  onBucketSelected: (evt) ->
+    @props.onStateChanged bucket: evt.option

   onAppSelectSearch: (evt) ->
     searchValue = evt.target.value
-    @setState appOptions: _.filter @props.apps, (app) -> (app.name.match(searchValue) or app.version.match(searchValue))
+    @props.onStateChanged appsearch: evt.target.value

   onNameChange: (evt,a,v) ->
-    @setState name: evt.target.value
+    @props.onStateChanged name: evt.target.value

   appToOptionValue: (app) ->
     value: "#{app.name} (#{app.version})"
     id: app._id
+    name: app.name
+    version: app.version
     label: <Box direction='row' justify='between'>{app.name}{app.version}</Box>

+  getAppOptions: ->
+    _.filter @props.apps, (app) => (app.name.match(@props.appsearch) or app.version.match(@props.appsearch))
+
   render: ->
-    console.log @props.apps
     <Box align='center' pad='medium'>
       <Form compact=false fill=false>

@@ -40,18 +50,49 @@
           </Box>

         <FormFields>
-          <FormField label='Application definition' htmlFor='app' size='medium'>
-            <Select placeHolder='Search'
-              inline={false}
-              multiple={false}
-              onSearch={@onAppSelectSearch}
-              options={(@state.appOptions or @props.apps).map @appToOptionValue}
-              value={@state.app}
-              onChange={@onAppSelectChange} />
-          </FormField>
-          <FormField label='Instance name' htmlFor='name' size='medium'>
-            <TextInput ref='name' placeHolder='A unique name to identify this instance' value={@state.name} onDOMChange={@onNameChange} />
-          </FormField>
+          <fieldset>
+            <FormField label='Application definition' htmlFor='app' size='medium'>
+              <Select placeHolder='Search'
+                inline={false}
+                multiple={false}
+                onSearch={@onAppSelectSearch}
+                options={(@getAppOptions() or @props.apps).map @appToOptionValue}
+                value={"#{@props.selectedAppName} (#{@props.selectedAppVersion})"}
+                onChange={@onAppSelectChange} />
+            </FormField>
+            <FormField label='Instance name' htmlFor='name' size='medium'>
+              <TextInput ref='name' placeHolder='A unique name to identify this instance' value={@props.name} onDOMChange={@onNameChange} />
+            </FormField>
+          </fieldset>
+          {if @props.selectedApp
+            params = @getAppParams()
+            <fieldset>
+              {if params.length
+                <Box direction='row' justify='between'>
+                  <Heading tag='h3'>App parameters</Heading>
+                </Box>
+              }
+
+              {params.map (param) ->
+                <FormField key={param} label={param} htmlFor={param} size='medium'>
+                  <TextInput  />
+                </FormField>
+              }
+            </fieldset>
+          }
+
+          <fieldset>
+            <Box direction='row' justify='between'>
+              {
+                heading = <Heading style={paddingTop:10} tag='h3'>Persisted Storage</Heading>
+                <CheckBox label={heading} toggle={true} defaultChecked />
+              }
+            </Box>
+
+            <FormField label='Storage bucket' size='medium' >
+              <Select onSearch={-> console.log 'search'} value={@props.bucket or @props.name} onChange={@onBucketSelected} options={@props.buckets}/>
+            </FormField>
+          </fieldset>
         </FormFields>
       </Form>
     </Box>


Add tests for try-catch-finally blocks.

Add tests for try-catch-finally blocks.

--- spec/powershell-spec.coffee
+++ spec/powershell-spec.coffee
@@ -150,6 +150,24 @@
           expect(tokens[5].value).toEqual "in"
           expect(tokens[5]).toHaveScope "keyword.control.flow.powershell"

+      describe "Try-Catch-Finally statements", ->
+        tokens = null
+
+        beforeEach ->
+          {tokens} = grammar.tokenizeLine("try { throw \"FAIL\" } catch [System.IO.IOException] { Get-OutOfTrouble } finally { Get-OutOfTown }")
+
+        it "should tokenize 'Try'", ->
+          expect(tokens[0].value).toEqual "try"
+          expect(tokens[0]).toHaveScope "keyword.control.flow.powershell"
+
+        it "should tokenize 'Catch'", ->
+          expect(tokens[8].value).toEqual "catch"
+          expect(tokens[8]).toHaveScope "keyword.control.flow.powershell"
+
+        it "should tokenize 'Finally'", ->
+          expect(tokens[16].value).toEqual "finally"
+          expect(tokens[16]).toHaveScope "keyword.control.flow.powershell"
+
     describe "Highlighting logical operators", ->
       logicalOperators = [ "-and", "-or", "-xor", "-not", "!"]


ASCII.

ASCII.

--- src/packet.coffee
+++ src/packet.coffee
@@ -118,15 +118,24 @@
     if parsing
       if not (value instanceof Buffer)
         value = bufferize(value)
+      if /^ascii$/i.test(encoding)
+        # Broken and waiting on [297](http://github.com/ry/node/issues/issue/297).
+        # If the top bit is set, it is not ASCII, so we zero the value.
+        for i in [0...value.length]
+          value[i] = 0 if value[i] & 0x80
+        encoding = "utf8"
       length = value.length
       length -= field.terminator.length if field.terminator
       value.toString(encoding, 0, length)
     else
       if field.terminator
         value += field.terminator
-      new Buffer(value, encoding)
-
-  # Broken and waiting on #[297](http://github.com/ry/node/issues/issue/297).
+      buffer = new Buffer(value, encoding)
+      if encoding is "ascii"
+        for i in [0...buffer.length]
+          buffer[i] = 0 if value.charAt(i) is '\0'
+      buffer
+
   ascii: (parsing, field, value) ->
     transforms.str("ascii", parsing, field, value)


Activity: partial fix for the routes

Activity: partial fix for the routes

--- client/Social/Activity/routes.coffee
+++ client/Social/Activity/routes.coffee
@@ -1,9 +1,25 @@
 do ->

+  createContentDisplayHandler = (section, passOptions = no) ->
+
+    ({params:{name, slug}, query}, models, route)->
+
+      {router} = KD.singletons
+      route = name unless route
+      contentDisplay = router.openRoutes[route.split('?')[0]]
+
+      if contentDisplay?
+        KD.singleton('display').hideAllDisplays contentDisplay
+        contentDisplay.emit 'handleQuery', query
+      else if models?
+        router.openContent name, section, models, route, query, passOptions
+      else
+        router.loadContent name, section, slug, route, query, passOptions
+
   KD.registerRoutes 'Activity',
-    '/:name?/Activity/:slug?' : ({params:{name, slug}, query})->
+
+    '/:name?/Activity/:slug?' : ({params:{name, slug}, query}) ->
       {router, appManager} = KD.singletons
-
       unless slug
       then router.openSection 'Activity', name, query
-      else router.createContentDisplayHandler('Activity') arguments...
+      else createContentDisplayHandler('Activity') arguments...


Filename in PDF preview tab title

Filename in PDF preview tab title

--- lib/viewer.coffee
+++ lib/viewer.coffee
@@ -2,6 +2,7 @@
 getCurrentWindow = require('electron').remote.getCurrentWindow
 BrowserWindow = require('electron').remote.BrowserWindow
 fs = require 'fs'
+path = require 'path'

 module.exports =
 class Viewer extends Disposable
@@ -99,7 +100,7 @@
     if @tabView? and atom.workspace.paneForItem(@tabView)?
       atom.workspace.paneForItem(@tabView).activateItem(@tabView)
     else
-      @tabView = new PDFView(@url)
+      @tabView = new PDFView(@url,"""#{path.basename(pdfPath)}""")
       atom.workspace.getActivePane().splitRight().addItem(@tabView)

   getUrl: ->
@@ -112,15 +113,16 @@
     return true

 class PDFView
-  constructor: (url) ->
+  constructor: (url,tabTitle) ->
     @element = document.createElement 'iframe'
     @element.setAttribute 'src', url
     @element.setAttribute 'width', '100%'
     @element.setAttribute 'height', '100%'
-    @element.setAttribute 'frameborder', 0
+    @element.setAttribute 'frameborder', 0,
+    @tabTitle = tabTitle

-  getTitle: ->
-    return 'Atom-LaTeX PDF Viewer'
+  getTitle:  ->
+    return """Atom-LaTeX - #{@tabTitle}"""

   serialize: ->
     return @element.getAttribute 'src'


Add focus to Cc and Bcc fields, refs #322

--- client/app/components/compose.coffee
+++ client/app/components/compose.coffee
@@ -226,6 +226,20 @@

     componentDidMount: ->
         @_initCompose()
+
+    componentDidUpdate: ->
+        switch @state.focus
+            when 'cc'
+                setTimeout ->
+                    document.getElementById('compose-cc').focus()
+                , 0
+                @setState focus: ''
+
+            when 'bcc'
+                setTimeout ->
+                    document.getElementById('compose-bcc').focus()
+                , 0
+                @setState focus: ''

     componentWillUnmount: ->
         if @_saveInterval
@@ -477,12 +491,14 @@
     onToggleCc: (e) ->
         toggle = (e) -> e.classList.toggle 'shown'
         toggle e for e in @getDOMNode().querySelectorAll '.compose-cc'
-        @setState ccShown: not @state.ccShown
+        focus = if not @state.ccShown then 'cc' else ''
+        @setState ccShown: not @state.ccShown, focus: focus

     onToggleBcc: (e) ->
         toggle = (e) -> e.classList.toggle 'shown'
         toggle e for e in @getDOMNode().querySelectorAll '.compose-bcc'
-        @setState bccShown: not @state.bccShown
+        focus = if not @state.bccShown then 'bcc' else ''
+        @setState bccShown: not @state.bccShown, focus: focus

 ComposeEditor = React.createClass


Ctrl-Enter send message

Ctrl-Enter send message

--- client/app/components/compose.coffee
+++ client/app/components/compose.coffee
@@ -139,6 +139,7 @@
                             className: 'rt-editor form-control',
                             ref: 'html',
                             contentEditable: true,
+                            onKeyDown: @onKeyDown,
                             dangerouslySetInnerHTML: {
                                 __html: @linkState('html').value
                             }
@@ -146,6 +147,7 @@
                         textarea
                             className: 'editor',
                             ref: 'content',
+                            onKeyDown: @onKeyDown,
                             defaultValue: @linkState('text').value

                 div className: 'attachements',
@@ -406,3 +408,7 @@
     onToggleBcc: (e) ->
         toggle = (e) -> e.classList.toggle 'shown'
         toggle e for e in @getDOMNode().querySelectorAll '.compose-bcc'
+
+    onKeyDown: (evt) ->
+        if evt.ctrlKey and evt.key is 'Enter'
+            @onSend()


remove console.log

remove console.log

--- lib/atom-ternjs-config.coffee
+++ lib/atom-ternjs-config.coffee
@@ -75,7 +75,6 @@
     @config = @mergeConfigObjects(@projectConfig, @config)
     if @projectConfig
       @config = @prepareLibs(@config, configStub)
-      console.log @config
       for plugin of @config.plugins
         @config.plugins[plugin]?.active = true
       @config = @mergeConfigObjects(configStub, @config)


Fix remote file paths on Windows at pre-upload check

--- src/deploy.coffee
+++ src/deploy.coffee
@@ -395,7 +395,7 @@
 		if @toUpload.length
 			console.log "Files that will be uploaded:".bold.blue
 			for file in @toUpload
-				remoteFile = path.normalize @config.path.remote + file.remote
+				remoteFile = @_constructRemotePath(path.normalize(@config.path.remote + file.remote))
 				console.log("[ ? ]".blue, "#{file.name}".blue, ">".green, "#{remoteFile}".blue)

 		prompt.start()
@@ -617,4 +617,4 @@
 	_resolveHomeFolder: (str) ->
 		homeFolder = (process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE)
 		return path.resolve path.join(homeFolder, str.substr(1)) if str.substr(0, 1) is "~"
-		str+		str


Handle case where @stop_waiting gets back null(s)

--- lib/worker.coffee
+++ lib/worker.coffee
@@ -356,10 +356,11 @@
     if (cached = @cache[key]) != undefined
       cached
     else if (cached = @manager.check_cache(key)) != undefined
+      msg = msgpack.pack(source: key, target: @key)
       @db.multi()
         .sadd('sources:'+@key, key)
         .sadd('targets:'+key, @key)
-        .publish('redeye:require', { source: key, target: @key})
+        .publish('redeye:require', msg)
         .exec (err) -> throw err if err
       @cache[key] = cached
     else
@@ -387,9 +388,18 @@
   # and resume the fiber with them.
   stop_waiting: ->
     @db.mget @waiting_for, (err, arr) =>
+      return @resume err if err
+      arr = for buf, index in arr
+        if arr
+          msgpack.unpack buf
+        else
+          (err ||= []).push @waiting_for[index]
+          undefined
       @waiting_for = null
-      arr = (msgpack.unpack buf for buf in arr) unless err
-      @resume err, arr
+      if err
+        @resume "Expected finished keys but got null: #{err.join ','}"
+      else
+        @resume null, arr

   # Because we're in an `@each` or `@all` block, don't attempt
   # to get the key yet; instead, just record the context and


Add United States to countries list

Add United States to countries list

--- both/collections/users.coffee
+++ both/collections/users.coffee
@@ -230,6 +230,7 @@
       "Ukraine"
       "United Arab Emirates"
       "United Kingdom"
+      "United States"
       "Uruguay"
       "Uzbekistan"
       "Venezuela"


now fetches comments as well, just not the right amount - bugged

--- server/app/bongo/models/messages/discussion.coffee
+++ server/app/bongo/models/messages/discussion.coffee
@@ -305,7 +305,20 @@
         limit         : 5
         sort          :
           timestamp   : 1
-      .reverse()
+      .nodes()
+      .edges
+        query         :
+          sourceName  : 'JOpinion'
+          targetName  : 'JComment'
+          as          : 'reply'
+          'data.deletedAt':
+            $exists   : no
+          'data.flags.isLowQuality':
+            $ne       : yes
+        limit         : 3
+        sort          :
+          timestamp   : 1
+      .nodes()
       .and()
       .edges
         query         :


Server: Tests: refactor verifyslug handler test file

--- servers/lib/server/handlers/verifyslug.test.coffee
+++ servers/lib/server/handlers/verifyslug.test.coffee
@@ -1,35 +1,39 @@
-{ daisy
+{ async
   expect
   request
-  generateRandomString }            = require '../../../testhelper'
+  generateRandomString
+  checkBongoConnectivity }          = require '../../../testhelper'
 { generateVerifySlugRequestParams } = require '../../../testhelper/handler/verifyslughelper'
 { generateCreateTeamRequestParams } = require '../../../testhelper/handler/teamhelper'

 reservedTeamDomains = require '../../../../workers/social/lib/social/models/user/reservedteamdomains'

+beforeTests = -> before (done) ->
+
+  checkBongoConnectivity done
+
+
 runTests = -> describe 'server.handlers.verifyslug', ->

   it 'should send HTTP 404 if request method is not POST', (done) ->

-    queue       = []
-    methods     = ['put', 'patch', 'delete']
+    queue   = []
+    methods = ['put', 'patch', 'delete']

     methods.forEach (method) ->
       verifySlugRequestParams = generateVerifySlugRequestParams
-        method  : method
-        body    :
-          name  : 'some-domain'
+        method : method
+        body   :
+          name : 'some-domain'

-      queue.push ->
+      queue.push (next) ->
         request verifySlugRequestParams, (err, res, body) ->
-          expect(err)             .to.not.exist
-          expect(res.statusCode)  .to.be.equal 404
-          queue.next()
+          expect(err).to.not.exist
+          expect(res.statusCode).to.be.equal 404
+          next()

-    queue.push -> done()
-
-    daisy queue
+    async.series queue, done

   describe 'when team domain is not set', ->
@@ -41,9 +45,9 @@
           name : ''

       request.post  verifySlugRequestParams, (err, res, body) ->
-        expect(err)             .to.not.exist
-        expect(res.statusCode)  .to.be.equal 400
-        expect(body)            .to.be.equal 'No domain param is given!'
+        expect(err).to.not.exist
+        expect(res.statusCode).to.be.equal 400
+        expect(body).to.be.equal 'No domain param is given!'
         done()

@@ -73,16 +77,14 @@
           body   :
             name : invalidTeamDomain

-        queue.push ->
+        queue.push (next) ->
           request.post verifySlugRequestParams, (err, res, body) ->
-            expect(err)             .to.not.exist
-            expect(res.statusCode)  .to.be.equal 400
-            expect(body)            .to.be.equal 'Invalid domain!'
-            queue.next()
+            expect(err).to.not.exist
+            expect(res.statusCode).to.be.equal 400
+            expect(body).to.be.equal 'Invalid domain!'
+            next()

-      queue.push -> done()
-
-      daisy queue
+      async.series queue, done

   describe 'when domain is available', ->

@@ -105,16 +107,14 @@
           body   :
             name : validTeamDomain

-        queue.push ->
+        queue.push (next) ->
           request.post verifySlugRequestParams, (err, res, body) ->
-            expect(err)             .to.not.exist
-            expect(res.statusCode)  .to.be.equal 200
-            expect(body)            .to.be.equal 'Domain is available!'
-            queue.next()
+            expect(err).to.not.exist
+            expect(res.statusCode).to.be.equal 200
+            expect(body).to.be.equal 'Domain is available!'
+            next()

-      queue.push -> done()
-
-      daisy queue
+      async.series queue, done

   describe 'when domain is taken', ->
@@ -129,29 +129,27 @@

       queue = [

-        ->
+        (next) ->
           options = { body : { slug } }
           generateCreateTeamRequestParams options, (createTeamRequestParams) ->

             # expecting team to be created
             request.post createTeamRequestParams, (err, res, body) ->
-              expect(err)             .to.not.exist
-              expect(res.statusCode)  .to.be.equal 200
-              queue.next()
+              expect(err).to.not.exist
+              expect(res.statusCode).to.be.equal 200
+              next()

-        ->
+        (next) ->
           # expecting HTTP 400 when domain is taken
           request.post verifySlugRequestParams, (err, res, body) ->
-            expect(err)             .to.not.exist
-            expect(res.statusCode)  .to.be.equal 400
-            expect(body)            .to.be.equal 'Domain is taken!'
-            queue.next()
-
-        -> done()
+            expect(err).to.not.exist
+            expect(res.statusCode).to.be.equal 400
+            expect(body).to.be.equal 'Domain is taken!'
+            next()

       ]

-      daisy queue
+      async.series queue, done

   describe 'when domain is a reserved one', ->
@@ -166,10 +164,12 @@

       # expecting HTTP 400 when domain is taken
       request.post verifySlugRequestParams, (err, res, body) ->
-        expect(err)             .to.not.exist
-        expect(res.statusCode)  .to.be.equal 400
-        expect(body)            .to.be.equal 'Invalid domain!'
+        expect(err).to.not.exist
+        expect(res.statusCode).to.be.equal 400
+        expect(body).to.be.equal 'Invalid domain!'
         done()

+beforeTests()
+
 runTests()


shift() multiple little arrays. Avoid.

shift() multiple little arrays. Avoid.

--- src/editor.coffee
+++ src/editor.coffee
@@ -1761,6 +1761,7 @@
     line.push("")

   @buildEmptyLineHtml: (showIndentGuide, eolInvisibles, htmlEolInvisibles, indentation, activeEditSession, mini) ->
+    indentCharIndex = 0
     if not mini and showIndentGuide
       if indentation > 0
         tabLength = activeEditSession.getTabLength()
@@ -1768,7 +1769,7 @@
         for level in [0...indentation]
           indentLevelHtml = ""
           for characterPosition in [0...tabLength]
-            if invisible = eolInvisibles.shift()
+            if invisible = eolInvisibles[indentCharIndex++]
               indentLevelHtml += "#{invisible}"
             else
               indentLevelHtml += ' '


fix deleteText

fix deleteText

should not always mergeLines

--- src/editor.coffee
+++ src/editor.coffee
@@ -16,17 +16,17 @@
     [firstLine, offset] = @doc.findLineAtOffset(index)
     curLine = firstLine
     while curLine? and length > 0
-      deleteLength = Math.min(length, curLine.length - offset)
+      deleteLength = Math.min(length, curLine.length - offset + 1)
       nextLine = curLine.next
-      if curLine.length == deleteLength
+      if deleteLength <= curLine.length
+        curLine.deleteText(offset, deleteLength)
+      else
         ScribeDOM.removeNode(curLine.node)
         @doc.removeLine(curLine)
-      else
-        curLine.deleteText(offset, deleteLength)
       length -= deleteLength
       curLine = nextLine
       offset = 0
-    @doc.mergeLines(firstLine, firstLine.next) if firstLine?
+    @doc.mergeLines(firstLine, firstLine.next) if firstLine?.next != nextLine
   )

 # formatAt (Number index, Number length, String name, Mixed value) ->


Change visibility on the Editor Coordinates section

--- src/editor.coffee
+++ src/editor.coffee
@@ -1263,7 +1263,7 @@
   Section: Editor Coordinates
   ###

-  # Public: Convert a position in buffer-coordinates to screen-coordinates.
+  # Essential: Convert a position in buffer-coordinates to screen-coordinates.
   #
   # The position is clipped via {::clipBufferPosition} prior to the conversion.
   # The position is also clipped via {::clipScreenPosition} following the
@@ -1275,7 +1275,7 @@
   # Returns a {Point}.
   screenPositionForBufferPosition: (bufferPosition, options) -> @displayBuffer.screenPositionForBufferPosition(bufferPosition, options)

-  # Public: Convert a position in screen-coordinates to buffer-coordinates.
+  # Essential: Convert a position in screen-coordinates to buffer-coordinates.
   #
   # The position is clipped via {::clipScreenPosition} prior to the conversion.
   #
@@ -1285,21 +1285,21 @@
   # Returns a {Point}.
   bufferPositionForScreenPosition: (screenPosition, options) -> @displayBuffer.bufferPositionForScreenPosition(screenPosition, options)

-  # Public: Convert a range in buffer-coordinates to screen-coordinates.
+  # Essential: Convert a range in buffer-coordinates to screen-coordinates.
   #
   # * `bufferRange` {Range} in buffer coordinates to translate into screen coordinates.
   #
   # Returns a {Range}.
   screenRangeForBufferRange: (bufferRange) -> @displayBuffer.screenRangeForBufferRange(bufferRange)

-  # Public: Convert a range in screen-coordinates to buffer-coordinates.
+  # Essential: Convert a range in screen-coordinates to buffer-coordinates.
   #
   # * `screenRange` {Range} in screen coordinates to translate into buffer coordinates.
   #
   # Returns a {Range}.
   bufferRangeForScreenRange: (screenRange) -> @displayBuffer.bufferRangeForScreenRange(screenRange)

-  # Public: Clip the given {Point} to a valid position in the buffer.
+  # Extended: Clip the given {Point} to a valid position in the buffer.
   #
   # If the given {Point} describes a position that is actually reachable by the
   # cursor based on the current contents of the buffer, it is returned
@@ -1320,7 +1320,7 @@
   # Returns a {Point}.
   clipBufferPosition: (bufferPosition) -> @buffer.clipPosition(bufferPosition)

-  # Public: Clip the start and end of the given range to valid positions in the
+  # Essential: Clip the start and end of the given range to valid positions in the
   # buffer. See {::clipBufferPosition} for more information.
   #
   # * `range` The {Range} to clip.
@@ -1328,7 +1328,7 @@
   # Returns a {Range}.
   clipBufferRange: (range) -> @buffer.clipRange(range)

-  # Public: Clip the given {Point} to a valid position on screen.
+  # Essential: Clip the given {Point} to a valid position on screen.
   #
   # If the given {Point} describes a position that is actually reachable by the
   # cursor based on the current contents of the screen, it is returned
@@ -1352,9 +1352,6 @@
   #
   # Returns a {Point}.
   clipScreenPosition: (screenPosition, options) -> @displayBuffer.clipScreenPosition(screenPosition, options)
-
-
-

   ###
   Section: Grammars


added a resize editor method

added a resize editor method

--- src/editor.coffee
+++ src/editor.coffee
@@ -14,12 +14,6 @@
 editor.setTheme require "ace/theme/twilight"
 editor.getSession().setUseSoftTabs true
 editor.getSession().setTabSize 2
-
-# fuuuuu, ui bug
-setTimeout ->
-  editor.focus()
-  editor.resize()
-, 200

 filename = null
 editor.getSession().on 'change', ->
@@ -57,6 +51,11 @@
       win: null
       mac: shortcut
       sender: 'editor'
+exports.resize = (e) ->
+  setTimeout ->
+    editor.focus()
+    editor.resize()
+  , 200

 bindKey 'open', 'Command-O', (env, args, request) ->


Extract mock_method.restore_original_method()

--- src/TinyMock.coffee
+++ src/TinyMock.coffee
@@ -33,6 +33,11 @@
       mock_method.method_name = method_name
       mock_method.original_method = @[method_name]
       mock_method.expectations = expectations = new ExpectationList()
+      mock_method.restore_original_method = ->
+        if @original_method?
+          @object[@method_name] = @original_method
+        else
+          delete @object[@method_name]
       @[method_name] = mock_method
       mock_methods.add(mock_method)
     expectations.create_expectation()
@@ -146,11 +151,8 @@
     errors

   restore_original_methods: ->
-    for mock_method in @_list
-      if mock_method.original_method?
-        mock_method.object[mock_method.method_name] = mock_method.original_method
-      else
-        delete mock_method.object[mock_method.method_name]
+    mock_method.restore_original_method() for mock_method in @_list
+

 #


follow docs

follow docs

--- src/follow.coffee
+++ src/follow.coffee
@@ -2,7 +2,7 @@
 The Follow module provides a simple method to set an object's
 velocity so that it will approach another object.

-The calculate
+The calculated velocity is

 This method relies on both objects having `position` methods.
 All GameObjects have this method by default.


follow docs

follow docs

--- src/follow.coffee
+++ src/follow.coffee
@@ -2,7 +2,7 @@
 The Follow module provides a simple method to set an object's
 velocity so that it will approach another object. This method
 relies on both objects having `bounds` methods. GameObject
-provides this method by defau
+provides this method by default

 player = GameObject


* [bug] do not add a key to cache if popped is true

--- src/cache-factory.coffee
+++ src/cache-factory.coffee
@@ -73,8 +73,10 @@
         result = instanceCache.get(cachedName)
         if result is undefined
           result = createObject aClass, undefined, aOptions
-          cached = undefined unless isObject cached
-          instanceCache.set cachedName, result, cached
+          unless popped
+            cached = undefined unless isObject cached
+            result.cached = cachedName
+            instanceCache.set cachedName, result, cached
         else if popped
           instanceCache.del(cachedName)
     result


Removed unused `noCaret` property

Removed unused `noCaret` property

--- lib.coffee
+++ lib.coffee
@@ -361,7 +361,6 @@
     format: format
     datePart: "YYYY-MM-DD"
     timePart: "HH:mm:ss"
-    noCaret: yes
     dateGlyph: "calendar"
     timeGlyph: "time"
     defaultValue: (do moment).format format
@@ -409,7 +408,7 @@
     @changeValue "time"

   render: ->
-    {label, help, addonBefore, addonAfter, format, datePart, timePart, dateGlyph, timeGlyph, noCaret, dropup, bsStyle} = @props
+    {label, help, addonBefore, addonAfter, format, datePart, timePart, dateGlyph, timeGlyph, dropup, bsStyle} = @props
     {value, dateView} = @state
     datetime = moment value, format

@@ -418,7 +417,7 @@
         createElement ButtonGroup, null,
           if datePart
             createElement DropdownButton,
-              noCaret: noCaret
+              noCaret: yes
               dropup: dropup
               bsStyle: bsStyle
               title: [
@@ -461,7 +460,7 @@
                   value: value
           if timePart
             createElement DropdownButton,
-              noCaret: noCaret
+              noCaret: yes
               dropup: dropup
               bsStyle: bsStyle
               title: [


STYLE: merged two lines

STYLE: merged two lines

--- src/github.coffee
+++ src/github.coffee
@@ -17,8 +17,7 @@

 class GithubCommits extends Timeline
     constructor: (@username, @repo, options) ->
-        @queryUrl = "https://api.github.com/repos"
-        @queryUrl += "/#{@username}/#{@repo}/commits"
+        @queryUrl = "https://api.github.com/repos/#{@username}/#{@repo}/commits"
         super options

     fetch: -> jsonp


Adding Wiki support

Adding Wiki support

--- src/gitlab.coffee
+++ src/gitlab.coffee
@@ -134,6 +134,14 @@
         # not code? must be a something good!
         else
           switch hook.object_kind
+            when "wiki_page"
+              text = "Wiki - {bold(hook.object_attributes.title)}: #{hook.object_attributes.action} at #{hook.object_attributes.url}"
+
+              splitted = hook.object_attributes.content.split  "\r\n"
+              for i in [0...splitted.length]
+                splitted[i] = ">> " + splitted[i]
+              text += "\r\n" + splitted.join "\r\n"
+              robot.send user, text
             when "issue"
               unless hook.object_attributes.action == "update"
               # for now we don't trigger on update because on manual close it triggers close and update


Make set username screen conditional on Config setting. Fixes #20

--- both/router.coffee
+++ both/router.coffee
@@ -4,7 +4,7 @@
   notFoundTemplate: "notFound"
   routeControllerNameConverter: "camelCase"
   onBeforeAction: ->
-    if Meteor.userId() and not Meteor.user().username
+    if Config.username and Meteor.userId() and not Meteor.user().username
       @redirect '/setUserName'
     @next()

@@ -35,7 +35,7 @@
   @route "setUserName",
     path: "/setUserName"
     onBeforeAction: ->
-      if Meteor.userId() and Meteor.user().username
+      if not Config.username or (Meteor.userId() and Meteor.user().username)
         @redirect '/dashboard'
       @next()


heroku-startuptimes: More code, can extract starting->started times

--- src/heroku.coffee
+++ src/heroku.coffee
@@ -56,6 +56,8 @@
   for m in matches
     [_full, timestamp, target, dyno, info] = m

+    timestamp = new Date(timestamp)
+
     # known things to ignore
     if startsWith info, 'info'
     else if startsWith info, 'warn'
@@ -80,9 +82,10 @@
     else if info.indexOf('noflo-runtime-msgflo:error') != -1

     # events we care about
-    # FIXME: parse timestamps
     else if startsWith info, 'Scale to'
       events.push { type: 'scale-to', time: timestamp, msg: info }
+    # note: can be up or down, on multiple dynos.
+    # Should we synthesize per-dyno events from it? requires context...

     else if startsWith info, 'State changed from up to down'
       events.push { type: 'up->down', time: timestamp, dyno: dyno, msg: info }
@@ -91,23 +94,70 @@

     else if startsWith info, 'Starting process with command'
       events.push { type: 'process-starting', time: timestamp, dyno: dyno, msg: info }
+    else if startsWith info, 'noflo-runtime-msgflo started'
+      events.push { type: 'process-started', time: timestamp, dyno: dyno, msg: info }
+
     else if startsWith info, 'Process exited with status'
       events.push { type: 'process-exited', time: timestamp, dyno: dyno, msg: info }
     else if startsWith info, 'Stopping all processes with SIGTERM'
       events.push { type: 'process-stopping', time: timestamp, dyno: dyno, msg: info }
-    else if startsWith info, 'noflo-runtime-msgflo started'
-      events.push { type: 'process-started', time: timestamp, dyno: dyno, msg: info }
+
     else
       #console.log info
   return events

+# Basically a finite state machine, one per dyno
+applyEvent = (state, event) ->
+  # DynoState:    requested  |  starting   |  up  |   stopping  | (exited)
+  state.lasttransition = {} if not state.lasttransition # 'dyno.N' -> lastTransition: Event }
+  state.dynostate = {} if not state.dynostate # 'dyno.N' -> DynoState
+  state.startups = [] if not state.startups
+
+
+  # TODO: reject invalid transitions.\
+  # Note, they can happen initially because we don't generally know initial state
+  if event.dyno
+    # Dyno-specific events
+    #console.log event.dyno, event.type
+    switch event.type
+      when 'process-starting'
+        #console.log 'starting', event.dyno
+        state.dynostate[event.dyno] = 'starting'
+        state.lasttransition[event.dyno] = event
+      when 'process-started'
+        #console.log 'started', event.dyno
+        if state.lasttransition[event.dyno] and state.dynostate[event.dyno] == 'starting'
+          s =
+            dyno: event.dyno
+            start: state.lasttransition[event.dyno]
+            end: event
+          s.duration = s.end.time.getTime() - s.start.time.getTime()
+          state.startups.push s
+
+          state.dynostate[event.dyno] = 'started'
+          state.lasttransition[event.dyno] = event
+        else
+          console.log 'invalid transition', event.type, state.dynostate[event.dyno]
+      when 'starting->up' then null
+
+      when 'up->down' then null
+      when 'process-stopping' then null
+      when 'process-exited' then null
+  else
+    # FIXME: handle. Maybe outside/before. Particularly scale-to?
+
+
 analyzeStartups = (filename, callback) ->
   fs = require 'fs'

+  state = {}
   fs.readFile filename, {encoding: 'utf-8'}, (err, contents) ->
     return callback err if err
     events = eventsFromLog contents
-    results = events.map (e) -> "#{e.dyno or ''} #{e.type}"
+    #results = events.map (e) -> "#{e.dyno or ''} #{e.type}"
+    for e in events
+      applyEvent state, e
+    results = state.startups.map (s) -> s.duration
     return callback null, results

 exports.startuptime_main = () ->


make factory objects and arrays work

make factory objects and arrays work

--- src/hinoki.coffee
+++ src/hinoki.coffee
@@ -252,21 +252,36 @@
     return Promise.resolve result

   hinoki.callFactoryObjectArray = (path, factoryObject, dependenciesObject) ->
-    iterator = (f) ->
+    iterator = (f, key) ->
+      newPath = path.slice()
+      newPath[0] += '[' + key + ']'
+
       if 'function' is typeof f
         names = hinoki.getNamesToInject f
         dependencies = _.map names, (name) ->
           dependenciesObject[name]
-        return hinoki.callFactoryFunction(path, f, dependencies)
+        return hinoki.callFactoryFunction(newPath, f, dependencies)
+      else if 'object' is typeof f
+        # supports nesting
+        return hinoki.callFactoryObjectArray(newPath, f, dependenciesObject)
       else
-        # supports nesting
-        return hinoki.callFactoryObjectArray(path, f, dependenciesObject)
+        throw new Error "f must be either array or object but is #{typeof f}"

     if Array.isArray factoryObject
       Promise.all(factoryObject).map(iterator)
     # object !
-    else
-      Promise.props _.mapValues factoryObject, iterator
+    else if 'object' is typeof factoryObject
+      keys = Object.keys(factoryObject)
+      length = keys.length
+      i = -1
+      result = {}
+      while ++i < length
+        key = keys[i]
+        unless key is '__inject'
+          result[key] = iterator(factoryObject[key], key)
+      Promise.props result
+    else
+      throw new Error "factoryObject must be either array or object but is #{typeof factoryObject}"

   hinoki.callFactory = (path, factory, dependencyValues) ->
     if 'function' is typeof factory


Adjusting refactor to better emulate map.

--- scope.coffee
+++ scope.coffee
@@ -19,11 +19,20 @@
   # it wraps.
   constructor: (@parent, @expressions, @method) ->
     @variables = [{name: 'arguments', type: 'arguments'}]
+    @positions = {}
     if @parent
       @garbage = @parent.garbage
     else
       @garbage   = []
       Scope.root = this
+
+  # Adds a new variable or overrides an existing one.
+  setVar: (name, type) ->
+    if @positions.hasOwnProperty name
+      @variables.splice @positions[name], 1, {name, type}
+    else
+      @positions[name] = @variables.length
+      @variables.push {name, type}

   # Create a new garbage level
   startLevel: ->
@@ -42,7 +51,7 @@
   # already exist.
   find: (name, options) ->
     return true if @check name, options
-    @variables.push {name, type: 'var'}
+    @setVar name, 'var'
     false

   # Test variables and return true the first time fn(v, k) returns true
@@ -54,7 +63,7 @@
   # Reserve a variable name as originating from a function parameter for this
   # scope. No `var` required for internal references.
   parameter: (name) ->
-    @variables.push {name, type: 'param'}
+    @setVar name, 'param'

   # Just check to see if a variable has already been declared, without reserving,
   # walks up to the root scope.
@@ -80,14 +89,14 @@
   freeVariable: (type) ->
     index = 0
     index++ while @check(temp = @temporary type, index) and @type(temp) isnt 'reuse'
-    @variables.push {name: temp, type: 'var'}
+    @setVar temp, 'var'
     last(@garbage).push temp if @garbage.length
     temp

   # Ensure that an assignment is made at the top of this scope
   # (or at the top-level scope, if requested).
   assign: (name, value) ->
-    @variables.push {name, type: { value: value, assigned: true }}
+    @setVar name, value: value, assigned: true

   # Does this scope reference any variables that need to be declared in the
   # given function body?


Don't handle package events from SettingsView

--- lib/settings-view.coffee
+++ lib/settings-view.coffee
@@ -33,7 +33,6 @@
   initialize: ({@uri, activePanelName}={}) ->
     super
     @packageManager = new PackageManager()
-    @handlePackageEvents()

     @panelToShow = activePanelName
     process.nextTick => @initializePanels()
@@ -44,11 +43,6 @@
   #TODO Remove both of these post 1.0
   onDidChangeTitle: -> new Disposable()
   onDidChangeModified: -> new Disposable()
-
-  handlePackageEvents: ->
-    @subscribe @packageManager, 'package-installed theme-installed', ({name}) =>
-      if pack = atom.packages.getLoadedPackage(name)
-        @addPackagePanel(pack)

   initializePanels: ->
     return if @panels.size > 0


Command scope-selector 'atom-text-editor' -> 'atom-workspace'

The `sync-settings:upload` and `sync-settings:download` commands only showed up in the command palette while inside a text editor, so not if in settings view or basically anything that isn't a text editor. With this change the commands always show up in the command palette

--- lib/sync-settings.coffee
+++ lib/sync-settings.coffee
@@ -16,8 +16,8 @@

   activate: ->
     # for debug
-    atom.commands.add 'atom-text-editor', "sync-settings:upload", => @upload()
-    atom.commands.add 'atom-text-editor', "sync-settings:download", => @download()
+    atom.commands.add 'atom-workspace', "sync-settings:upload", => @upload()
+    atom.commands.add 'atom-workspace', "sync-settings:download", => @download()

   deactivate: ->


move for clarity

move for clarity

--- lib/manifest.coffee
+++ lib/manifest.coffee
@@ -24,7 +24,6 @@
     id     = uuid.v1()
     buffer = new Buffer(JSON.stringify(@manifest), "binary")
     @generate_put_url id, (err, slug_put_url) =>
-      put = @knox.put "/manifest/#{id}.json", "Content-Length":buffer.length, "Content-Type":"application/json"
       env =
         ANVIL_HOST:    process.env.ANVIL_HOST
         BUILDPACK_URL: @buildpack_with_default(options.buildpack)
@@ -38,6 +37,7 @@
         SLUG_PUT_URL:  slug_put_url
       for key, val of options.env
         env[key] = val
+      put = @knox.put "/manifest/#{id}.json", "Content-Length":buffer.length, "Content-Type":"application/json"
       put.on "response", (res) ->
         builder = spawner.spawn("bin/compile \"#{id}\"", env:env)
         cb id, builder


Removed unnecessary statement

Removed unnecessary statement

--- js-library/app/js/loader.coffee
+++ js-library/app/js/loader.coffee
@@ -9,7 +9,6 @@

 iframeLoaded = false

-iframeDoc = undefined
 iframe.style.display = 'block'
 iframe.style.border = '0px solid transparent'
 iframe.id = 'factlink-iframe'


namespaced unbind

namespaced unbind

--- src/bindable.coffee
+++ src/bindable.coffee
@@ -45,7 +45,7 @@
   ###
   bind: (events, callback) ->
     events.split(" ").each (namespacedEvent) ->
-      [event, namespaces...] = namespacedEvent.split(".")
+      [event, namespace] = namespacedEvent.split(".")

       eventCallbacks[event] = eventCallbacks[event] || []
       eventCallbacks[event].push(callback)


add tests for message2 format of support_ticket calls

--- src/smc-hub/test/api/messages.coffee
+++ src/smc-hub/test/api/messages.coffee
@@ -17,21 +17,28 @@
     it "gets definition for ping message", ->
         expect(messages.ping({})).toEqual({'event':'ping'})

-describe 'checking message2 features', ->
-    it "checks ping documentation", ->
+describe 'checking message2 documentation', ->
+    it "ping", ->
         expect(messages.documentation.events.ping.description).toInclude("curl -X POST")
-    it "checks get_usernames documentation", ->
+    it "get_usernames", ->
         expect(messages.documentation.events.get_usernames.description).toInclude("/api/v1/get_usernames")
-    it "checks create_account documentation", ->
+    it "create_account", ->
         expect(messages.documentation.events.create_account.description).toInclude("/api/v1/create_account")
-    it "checks delete_account documentation", ->
+    it "delete_account", ->
         expect(messages.documentation.events.delete_account.description).toInclude("/api/v1/delete_account")
-    it "checks create_project documentation", ->
+    it "create_project", ->
         expect(messages.documentation.events.create_project.description).toInclude("/api/v1/create_project")
-    it "checks query documentation", ->
+    it "query", ->
         expect(messages.documentation.events.query.description).toInclude("/api/v1/query")
-    it "checks change_email_address documentation", ->
+    it "change_email_address", ->
         expect(messages.documentation.events.change_email_address.description).toInclude("set a new email address")
         expect(messages.documentation.events.change_email_address.fields.account_id).toMatch('required')
         expect(messages.documentation.events.change_email_address.fields.new_email_address).toMatch('required')
+    it "create_support_ticket", ->
+        expect(messages.documentation.events.create_support_ticket.description).toInclude("/api/v1/create_support_ticket")
+        expect(messages.documentation.events.create_support_ticket.fields.email_address).toMatch('required')
+        expect(messages.documentation.events.create_support_ticket.fields.subject).toMatch('required')
+        expect(messages.documentation.events.create_support_ticket.fields.body).toMatch('required')
+    it "get_support_tickets", ->
+        expect(messages.documentation.events.get_support_tickets.description).toInclude("/api/v1/get_support_tickets")


adds api for loading initialized users

adds api for loading initialized users

--- js/src/http/DirectoryApi.coffee
+++ js/src/http/DirectoryApi.coffee
@@ -161,9 +161,17 @@
         wrappedRequest = Requests.wrapCredentials(request, { principal, credential })
         axios(wrappedRequest)

+    # gets users in the specified realm (initialized users only)
+    getInitializedUsers: ({ realm }) ->
+      Promise.resolve(axios(Requests.wrapCredentials({
+        url    : usersInRealmUrl() + '/initialized/' + realm
+        method : 'GET'
+      })))
+      .then (response) =>
+        uuids = response.data
+        return uuids

-    # gets users in the specified realm.
-    # does not include uninitialized users who have not set their primary key yet.
+    # gets users in the specified realm (includes users who have not initialized salt/publicKey)
     getUsers: ({ realm }) ->
       Promise.resolve(axios(Requests.wrapCredentials({
         url    : usersInRealmUrl() + '/' + realm
@@ -171,7 +179,6 @@
       })))
       .then (response) =>
         uuids = response.data
-        log.info('getUsers', uuids)
-        return Promise.filter(uuids, (uuid) => @getPublicKey(uuid))
+        return uuids

   return DirectoryApi


Rework item removal/update methods.

Rework item removal/update methods.

--- src/checkout.coffee
+++ src/checkout.coffee
@@ -177,7 +177,7 @@
 			data: JSON.stringify(updateItemsRequest)
 		.done @cacheOrderForm

-	# Sends a request to update the items in the OrderForm.
+	# Sends a request to update the items in the OrderForm. Items that are omitted are not modified.
 	# @param items [Array] an array of objects representing the items in the OrderForm.
 	# @param expectedOrderFormSections [Array] (default = *all*) an array of attachment names.
 	# @return [Promise] a promise for the updated OrderForm.
@@ -199,19 +199,20 @@
 		.done => @_requestingItem = undefined

 	# Sends a request to remove items from the OrderForm.
-	# @param items [Array] an array of objects representing the items to remove.
+	# @param items [Array] an array of objects representing the items to remove. These objects must have at least the `index` property.
 	# @return [Promise] a promise for the updated OrderForm.
 	removeItems: (items) =>
-#		if orderFormHasExpectedSections(@orderForm, ['items'])
-#
-#		else @getOrderForm(['items'])
-		# TODO usar copia local do OrderForm
-		deferred = $.Deferred()
-		promiseForItems = if items then $.when(items) else @getOrderForm(['items']).then (orderForm) -> orderForm.items
-		promiseForItems.then (array) =>
-			@updateItems(_(array).map((item, i) => {index: item.index, quantity: 0}).reverse())
-				.done((data) -> deferred.resolve(data)).fail(deferred.reject)
-		deferred.promise()
+		items.quantity = 0 for item in items
+		@updateItems items
+
+	# Sends a request to remove all items from the OrderForm.
+	# @return [Promise] a promise for the updated OrderForm.
+	removeAllItems: =>
+		orderFormPromise = if orderFormHasExpectedSections(['items']) then @promise(@orderForm) else @getOrderForm(['items'])
+		orderFormPromise.then (orderForm) =>
+			items = orderForm.items
+			item.quantity = 0 for item in items
+			@updateItems items

 	# Sends a request to add a discount coupon to the OrderForm.
 	# @param couponCode [String] the coupon code to add.