Handle bitbucket paged response

Handle bitbucket paged response

--- lib/util/bitbucket-manager.coffee
+++ lib/util/bitbucket-manager.coffee
@@ -2,6 +2,8 @@

 request = require 'request'
 q = require 'q'
+
+PAGE_SIZE = 50

 transformResponse = (body) ->
   if !body
@@ -32,11 +34,8 @@
       "sendImmediately": true
     }

-
-  # /maprtech/mapr.com-content/pullrequests
-  getPullRequests: (repoOwner, repoName) ->
+  _get: (url) ->
     deferred = q.defer()
-    url = "#{API_URL}#{repoOwner}/#{repoName}/pullrequests?pagelen=50"
     options =
       url: url
       auth: @buildAuth()
@@ -44,13 +43,44 @@

     request.get options, (error, response, body) ->
       try
-        console.log "API returned:", body
-        deferred.resolve transformResponse(body)
+        console.log "_get got", url, body
+        deferred.resolve body
       catch error
         deferred.reject error

     return deferred.promise

+  invokeTillHasNext: (url, transformFunction) ->
+    deferred = q.defer()
+    location= "#{url}?pagelen=#{PAGE_SIZE}"
+
+    #First invocation to know how many items we have
+    @_get(location).then (body) =>
+      firstPage = transformFunction(body)
+      if !body.next
+        deferred.resolve firstPage
+      else
+        pages = Math.ceil(body.size / PAGE_SIZE)
+        promises = []
+        promises.push(@_get(location + "&page=#{i}")) for i in [2 .. pages]
+
+        q.all(promises).then (resultBodies) ->
+          results = resultBodies.map (x) -> transformFunction(x)
+          toMerge = firstPage.concat(results)
+          deferred.resolve [].concat.apply([], toMerge)
+
+      return deferred.promise
+
+  # /maprtech/mapr.com-content/pullrequests
+  getPullRequests: (repoOwner, repoName) ->
+    deferred = q.defer()
+    url = "#{API_URL}#{repoOwner}/#{repoName}/pullrequests"
+
+    @invokeTillHasNext(url, transformResponse).then (result) ->
+      deferred.resolve result
+
+    return deferred.promise
+
   createPullRequest: (title, description, repoOwner, repoName, fromBranch, toBranch) ->
     deferred = q.defer()
     url = "#{API_URL}#{repoOwner}/#{repoName}/pullrequests"
@@ -85,18 +115,10 @@

   getBranches: (repoOwner, repoName) ->
     deferred = q.defer()
-    url = "#{API_URL}#{repoOwner}/#{repoName}/refs/branches?pagelen=100"
-    options =
-      url: url
-      auth: @buildAuth()
-      json: true
+    url = "#{API_URL}#{repoOwner}/#{repoName}/refs/branches"

-    request.get options, (error, response, body) ->
-      try
-        console.log "API returned:", body
-        deferred.resolve transformBranchResponse(body)
-      catch error
-        deferred.reject error
+    @invokeTillHasNext(url, transformBranchResponse).then (result) ->
+      deferred.resolve result

     return deferred.promise


Put a defaultPlaceHolder

Put a defaultPlaceHolder

--- client/landing/app/CommonViews/FormViews.coffee
+++ client/landing/app/CommonViews/FormViews.coffee
@@ -142,12 +142,14 @@

     {@memberData} = options
     {profile} = @memberData
-
+
+    defaultPlaceHolder = "You haven't entered anything in your bio yet. Why not add something now?"
+
     @aboutInput = new KDInputView
       cssClass      : 'about editable'
       type          : 'textarea'
-      defaultValue  : if profile.about is "You haven't entered anything in your bio yet. Why not add something now?" then '' else Encoder.htmlDecode profile.about
-      placeholder   : if profile.about isnt "You haven't entered anything in your bio yet. Why not add something now?" then null else Encoder.htmlDecode profile.about
+      defaultValue  : if profile.about is defaultPlaceHolder then '' else Encoder.htmlDecode profile.about
+      placeholder   : if profile.about isnt defaultPlaceHolder then null else Encoder.htmlDecode profile.about
       name          : 'about'

     @aboutInfo = new PersonalAboutView
@@ -155,7 +157,7 @@
         title     : "Click to edit"
         placement : "left"
         offset    : 5
-    , @memberData
+    , @memberData, defaultPlaceHolder

     @windowController = @getSingleton 'windowController'

@@ -172,7 +174,7 @@
   formCallback:(formData)->
     {profile} = @memberData
     {about} = formData
-    if profile.about is about
+    if profile.about is about or about is ''
       @unsetClass 'active'
       return no

@@ -190,6 +192,22 @@
           duration  : 500
         @unsetClass 'active'

+class PersonalAboutView extends KDCustomHTMLView
+  constructor:(options, data, defaultPlaceHolder)->
+    super
+    {profile} = @getData()
+    profile.about or= defaultPlaceHolder
+
+  viewAppended:->
+    super
+    @setTemplate @pistachio()
+    @template.update()
+
+  pistachio:->
+    """
+
{{ @utils.applyTextExpansions #(profile.about) }}

+    """
+
 class PersonalFormLocationView extends AbstractPersonalFormView
   constructor:(options, data)->
     options = $.extend
@@ -266,23 +284,6 @@

   getFirstLocation:(locationTags)->
     locationTags[0]
-
-class PersonalAboutView extends KDCustomHTMLView
-  constructor:(options, data)->
-    super
-
-  viewAppended:->
-    @setTemplate @pistachio()
-    @template.update()
-
-  pistachio:->
-    """
-
{{ @utils.applyTextExpansions #(profile.about) }}

-    """
-
-  getFirstLocation:(locationTags)->
-    locationTags[0]
-

 class PersonalFormSkillTagView extends KDFormView


minor

minor

--- client/landing/app/MainApp/book/bookdata.coffee
+++ client/landing/app/MainApp/book/bookdata.coffee
@@ -65,8 +65,8 @@
     embed     : "BookDevelopButton"
   ,
     title     : "Terminal"
-    content   : """
Terminal is a very important aspect of development, that's why we have invested a lot of time to provide a smooth, fast console.

-
Basically this is your virtual machine, that you're seeing its file of on your filetree. Whenever you signup we create this server for you. __CHANGE THIS COPY__
"""
+    content   : """
Terminal is a very important aspect of development, that's why we have invested a lot of time to provide a fast, smooth and responsive console.

+
__CHANGE THIS COPY__
"""
   ,
     title     : "Apps"
     content   : """
What makes Koding so useful are the apps provided by its users. Here you can perform one click installs of incredibly useful applications provided by users and major web development tools.


emit loaded data

emit loaded data

the Mobx models listen for this


Improve querystring serializer to detect numbers, nulls, undefined and booleans

--- src/utils/query-string-encoder.coffee
+++ src/utils/query-string-encoder.coffee
@@ -1,8 +1,23 @@
 Qs                = require('qs');

-module.exports =
+keywords =
+  true: true
+  false: false
+  null: null
+  undefined: undefined
+
+decoder = (value) ->
+  return parseFloat(value) if (/^(\d+|\d*\.\d+)$/.test(value))
+  return keywords[value] if (value of keywords)
+  return value
+
+
+module.exports =
   encode : (data)->
     Qs.stringify(data)
   decode : (search)->
-    return Qs.parse(window.location.search.slice(1)) unless search
+    return Qs.parse(
+      window.location.search.slice(1),
+      decoder: decoder
+    ) unless search
     Qs.parse(search)


add package to atom

add package to atom

--- atom/packages.cson
+++ atom/packages.cson
@@ -1,4 +1,6 @@
 packages: [
+  "atom-beautify"
   "file-icons"
+  "linter"
   "package-sync"
 ]


Add file-icons package to Atom

Add file-icons package to Atom

--- atom/packages.cson
+++ atom/packages.cson
@@ -2,6 +2,7 @@
   "auto-indent"
   "emmet"
   "expand-region"
+  "file-icons"
   "git-diff-popup"
   "git-plus"
   "git-time-machine"


Use 'uncaught-error' event from atom

Use 'uncaught-error' event from atom

--- lib/exception-reporting.coffee
+++ lib/exception-reporting.coffee
@@ -6,8 +6,9 @@
 module.exports =
   activate: ->
     atom.config.set('exception-reporting.userId', Guid.raw()) unless atom.config.get('exception-reporting.userId')
-    atom.on 'error.exception-reporting', (message, url, line) ->
-      Reporter.send(message, url, line) unless atom.inDevMode()
+    atom.on 'uncaught-error.exception', (message, url, line) ->
+      Reporter.send(message, url, line)
+

   deactivate: ->
-    atom.off 'error.exception-reporting'
+    atom.off 'uncaught-error.exception-reporting'


add brackets after completed functions

add brackets after completed functions

--- lib/runtime/completions.coffee
+++ lib/runtime/completions.coffee
@@ -48,3 +48,12 @@
   fromCache: (mod, prefix) ->
     @updateCache mod
     @processCompletions(@cache[mod] or [], prefix)
+
+  onDidInsertSuggestion: ({editor, suggestion: {type}}) ->
+    if type is 'function'
+      editor.mutateSelectedText (selection) ->
+        return unless selection.isEmpty()
+        {row, column} = selection.getBufferRange().start
+        if editor.getTextInBufferRange([[row, column], [row, column+1]]) isnt '('
+          selection.insertText '()'
+        selection.setBufferRange [[row, column+1], [row, column+1]]


Move preroll and id3 write to after the short-circuit for a HEAD request; other cleanup and rearranging

--- src/podroller/core.coffee
+++ src/podroller/core.coffee
@@ -152,47 +152,40 @@
     streamPodcast: (req, res, filename, size, stream_key, id3) ->
         return false if req.connection.destroyed

+        # Check if this is a range request
+        # False by default
+        rangeRequest = false
+
+        if _u.isString(req.headers.range)
+            rangeVals    = req.headers.range.match(/bytes ?= ?(\d+)-(\d+)?/)
+            rangeRequest = true
+
+
         @loadPreroll stream_key, req, (predata = null) =>
-            console.log "url is", req.url
-            console.log "request method is ", req.method
-
             # compute our final size
+            # If this isn't a range request, then this info won't
+            # be changed.
+            # If it IS a range request, then "length" will get
+            # changed to be the chunk size.
             fsize   = (id3?.length||0) + (predata?.length||0) + size
             fend    = fsize - 1
+            length  = fsize

             console.debug req.method, req.url
             console.debug "size:", fsize

             @listeners++

-            # Check if range request
-            # False by default
-            rangeRequest = false
-            length       = fsize
-
-            # Is the range header a string?
-            rangeStr = if _u.isString(req.headers.range) then req.headers.range else undefined
-            console.log "rangeStr is", rangeStr
-
-            # Get the requested start and end
-            if _u.isString rangeStr
-                rangeVals = rangeStr.match(/bytes ?= ?(\d+)-(\d+)?/)
-
-                if rangeVals
-                    # Request is for a range
-                    rangeRequest = true
-
-                    # Force into integers
-                    requestStart    = rangeVals[1] - 0
-                    requestEnd      = rangeVals[2] - 0 or undefined
-
-                    console.log "requested start, end is", requestStart, requestEnd
-
-                    rangeStart  = if (requestStart  <= fend) then requestStart else 0
-                    rangeEnd    = if (requestEnd    <= fend) then requestEnd   else fend
-                    console.log "rangeStart, rangeEnd, rangeRequest is", rangeStart, rangeEnd, rangeRequest
-
-                    length = (rangeEnd - rangeStart) + 1
+            if rangeRequest
+                # Get the requested start and end
+                # Force into integers
+                requestStart    = rangeVals[1] - 0
+                requestEnd      = rangeVals[2] - 0 or undefined
+
+                rangeStart  = if (requestStart  <= fend) then requestStart else 0
+                rangeEnd    = if (requestEnd    <= fend) then requestEnd   else fend
+
+                length = (rangeEnd - rangeStart) + 1

             # What is the actual length of content being sent back?
             console.debug "actual length is", length
@@ -214,47 +207,51 @@

             console.debug "response headers are", headers

-            # if we have an id3, write that
-            res.write id3 if id3
-
-            # write the preroll
-            res.write predata if predata
-
+            # If this is a HEAD request, we don't need to
+            # pipe the actual data to the client, so
+            # after setting all the headers, we're done.
             if req.method == "HEAD"
                 res.end()
-            else
-                # now set up our file read as a stream
-                console.log "creating read stream. #{@listeners} active downloads."
-                readStreamOpts = bufferSize: 256*1024
-
-                # If this is a range request, only deliver that range of bytes
-                if rangeRequest
-                    console.log "Sending byte range #{rangeStart}-#{rangeEnd}"
-                    readStreamOpts['start'] = rangeStart
-                    readStreamOpts['end']   = rangeEnd
-
-                console.log "read stream opts are", readStreamOpts
-                rstream = fs.createReadStream filename, readStreamOpts
-                rstream.pipe res, end:false
-
-                rstream.on "end", =>
-                    # got to the end of the file.  close our response
-                    console.log "(stream end) wrote #{ res.socket?.bytesWritten } bytes. #{@listeners} active downloads."
-                    res.end()
-
-                req.connection.on "end", =>
-                    # connection aborted.  destroy our stream
-                    console.log "(connection end) wrote #{ res.socket?.bytesWritten } bytes. #{@listeners} active downloads."
-                    rstream?.destroy() if rstream?.readable
-
-                req.connection.on "close", =>
-                    console.log "(conn close) in close. #{@listeners} active downloads."
-                    @listeners--
-
-                req.connection.setTimeout 30*1000, =>
-                    # handle connection timeout
-                    res.end()
-                    rstream?.destroy() if rstream?.readable
+                return true
+
+
+            # if we have an id3 or preroll, write them
+            res.write id3 if id3
+            res.write predata if predata
+
+
+            # now set up our file read as a stream
+            console.debug "creating read stream. #{@listeners} active downloads."
+            readStreamOpts = bufferSize: 256*1024
+
+            # If this is a range request, only deliver that range of bytes
+            if rangeRequest
+                console.debug "Sending byte range #{rangeStart}-#{rangeEnd}"
+                readStreamOpts['start'] = rangeStart
+                readStreamOpts['end']   = rangeEnd
+
+            console.debug "read stream opts are", readStreamOpts
+            rstream = fs.createReadStream filename, readStreamOpts
+            rstream.pipe res, end: false
+
+            rstream.on "end", =>
+                # got to the end of the file.  close our response
+                console.debug "(stream end) wrote #{ res.socket?.bytesWritten } bytes. #{@listeners} active downloads."
+                res.end()
+
+            req.connection.on "end", =>
+                # connection aborted.  destroy our stream
+                console.debug "(connection end) wrote #{ res.socket?.bytesWritten } bytes. #{@listeners} active downloads."
+                rstream?.destroy() if rstream?.readable
+
+            req.connection.on "close", =>
+                console.debug "(conn close) in close. #{@listeners} active downloads."
+                @listeners--
+
+            req.connection.setTimeout 30*1000, =>
+                # handle connection timeout
+                res.end()
+                rstream?.destroy() if rstream?.readable


fix a bug 将进度提示的关闭按钮去除

fix a bug
将进度提示的关闭按钮去除

--- lib/utils/progress-view.coffee
+++ lib/utils/progress-view.coffee
@@ -4,7 +4,7 @@
 class ProgressView extends View
   @content: ->
     @div class: 'butterfly overlay from-top', =>
-      @span click: 'destroy', class: 'glyphicon glyphicon-remove close-view'
+      # @span click: 'destroy', class: 'glyphicon glyphicon-remove close-view'
       @div class: 'block', style: 'text-align:center', =>
         @h2 outlet: 'title', class: 'block'
         @progress outlet: 'progress', class: '', style: 'width: 90%'


minor reorders in the visual core

minor reorders in the visual core

--- library/membrane/visual.coffee
+++ library/membrane/visual.coffee
@@ -264,22 +264,22 @@
         assert _.isFunction(@prelude), noPrelude
         assert _.isObject context = stock or {}
         @energizeContext.call @, context, symbol
-        assert prelude = @downstream prelude: =>
+        run = -> (f) -> f symbol, context, request
+        run prelude = this.downstream prelude: =>
             context.snapshot = try _.keys context
             assert @deployContext context, symbol
             assert @inlineAutocalls context, symbol
             context.inline -> @emit "installed", this
-            context.inline -> assert try @root = $root
             context.inline -> (@root.eco ?= []).push @
             context.inline -> this.externals.push "root"
             context.inline -> this.externals.push "eco"
+            context.inline -> assert try @root = $root
             context.inline -> assert @broadcast = ->
                 this.root.emit.apply $root, arguments
             assert context = @compressContext context
             return receive context, null unless asm
             @contextRendering request, context, (c) ->
                 return receive context, c.toString()
-        return prelude symbol, context, request

     # An internally used routine, part of the context assembly proc.
     # It is used to extend either a fresh or passed in stock object


minor changes/fixes and reorderings

minor changes/fixes and reorderings

--- library/nucleus/compose.coffee
+++ library/nucleus/compose.coffee
@@ -63,6 +63,40 @@
         w = get: -> assert not subject.watermark; subject
         Object.defineProperty snapshot, "watermark", w
         assert snapshot.watermark; return snapshot
+
+    # Scan the supplied class and return an entire inheritance hierarchy
+    # of classes. The hierarchy is represented as an array of prototypes
+    # that follow in the order they appear in the chain: starting from
+    # the supplied class and up to the top. The class has to be a valid
+    # CoffeeScript class that posses all the necessary internal members.
+    Object.defineProperty Object::, "hierarchy",
+        enumerable: no, value: hierarchy = (subject) ->
+            assert _.isArray a = accumulate = new Array
+            subject = this unless _.isObject try subject
+            classed = _.isObject subject.__super__ or null
+            assert classed, "supplied object is not a class"
+            scanner = (fn) -> fn a while subject?; return a
+            return scanner _.identity (accumulate) -> try
+                subject = subject.__super__ or undefined
+                constructor = subject.constructor or null
+                return no unless _.isFunction constructor
+                accumulate.push try subject = constructor
+
+    # A method for comparing different classes for equality. Be careful
+    # as this method is very loose in terms of comparison and its main
+    # purpose is aiding in implementation of the composition mechanism
+    # rather than any else. Comparison algorithms is likely to change.
+    # This is used internally in the composition system implementation.
+    Object.defineProperty Object::, "similarWith",
+        enumerable: no, value: (archetype, loose) ->
+            isClass = _.isObject this.prototype
+            noClass = "the subject is not a class"
+            throw new Error noClass unless isClass
+            return yes if this is archetype or no
+            return yes if @watermark is archetype
+            return undefined unless loose is yes
+            return yes if @name is archetype.name
+            return yes if @nick is archetype.nick

     # A unique functionality built around the composition system. It
     # allows for an asynchronous way of calling a stream of methods,
@@ -111,21 +145,6 @@
             applied = _.map prepped, (fn) -> fxc fn
             bounded = callback?.bind(this) or (->)
             return async.series applied, bounded
-
-    # A method for comparing different classes for equality. Be careful
-    # as this method is very loose in terms of comparison and its main
-    # purpose is aiding in implementation of the composition mechanism
-    # rather than any else. Comparison algorithms is likely to change.
-    Object.defineProperty Object::, "similarWith",
-        enumerable: no, value: (archetype, loose) ->
-            isClass = _.isObject this.prototype
-            noClass = "The subject is not a class"
-            throw new Error noClass unless isClass
-            return yes if this is archetype or no
-            return yes if @watermark is archetype
-            return undefined unless loose is yes
-            return yes if @name is archetype.name
-            return yes if @nick is archetype.nick

     # A method for the dynamic lookup of the super methods. This method
     # exists because CoffeeScript resolves super methods by using static
@@ -198,24 +217,6 @@
             assert shadow, "original: #{left().identify()}"
             left().rebased right(); @refactoring trigger

-    # Scan the supplied class and return an entire inheritance hierarchy
-    # of classes. The hierarchy is represented as an array of prototypes
-    # that follow in the order they appear in the chain: starting from
-    # the supplied class and up to the top. The class has to be a valid
-    # CoffeeScript class that posses all the necessary internal members.
-    Object.defineProperty Object::, "hierarchy",
-        enumerable: no, value: (subject) ->
-            assert _.isArray a = accumulate = new Array
-            subject = this unless _.isObject try subject
-            classed = _.isObject subject.__super__ or null
-            assert classed, "supplied object is not a class"
-            scanner = (fn) -> fn a while subject?; return a
-            return scanner _.identity (accumulate) -> try
-                subject = subject.__super__ or undefined
-                constructor = subject.constructor or null
-                return no unless _.isFunction constructor
-                accumulate.push try subject = constructor
-
     # A fancy method for dynamically changing the inheritance chain of
     # the existing classes. This method rebases the current class to
     # use the supplied base class as its direct ancestor. The supplied


refactor the unsupported method

refactor the unsupported method

--- library/nucleus/restful.coffee
+++ library/nucleus/restful.coffee
@@ -104,16 +104,17 @@
     # method that has been used to make the request is not supported
     # by this service of the internals that are comprising service.
     # Can be used from the outside, but generally should not be done.
+    # Will be invoked if a method is not defined or not implemented.
     unsupported: (request, response, next) ->
-        assert methodNotAllowed = 405
-        assert codes = http.STATUS_CODES or {}
-        assert message = codes[methodNotAllowed]
-        doesJson = response.accepts(/json/) or no
+        assert codes = http.STATUS_CODES or Object()
+        assert methodNotAllowed = code = 405 # HTTP
+        assert message = try codes[methodNotAllowed]
+        doesJson = response.accepts(/json/) or false
         response.writeHead methodNotAllowed, message
-        descriptor = error: message, code: methodNotAllowed
+        descriptor = error: "#{message}", code: code
         @emit "unsupported", request, response, next
         return response.send descriptor if doesJson
-        response.send message; return this
+        response.send message.toString(); return @

     # This method determines whether the supplied HTTP request
     # matches this service. This is determined by examining the
@@ -121,7 +122,7 @@
     # that were used for configuring the class of this service.
     # It is async, so be sure to call the `decide` with boolean!
     matches: (request, response, decide) ->
-        conditions = try @constructor.condition()
+        conditions = try @constructor.condition() or []
         conditions = Array() unless _.isArray conditions
         identify = try @constructor?.identify().underline
         p = (i, cn) -> i.limitation request, response, cn


allow only certain root content

allow only certain root content

--- library/nucleus/restful.coffee
+++ library/nucleus/restful.coffee
@@ -181,9 +181,10 @@
     # system of service hooks. Refer to the original sender for
     # more information on how the content is encoded and passed.
     push: (response, content) ->
-        isContent = content isnt undefined
-        noContent = "No valid content supplied"
-        throw new Error noContent unless isContent
+        isArray = content? and _.isArray content
+        isObject = content? and _.isObject content
+        noContent = "no valid root content supplied"
+        assert isArray or isObject, "#{noContent}"
         try @emit "push", this, response, content
         uploader = -> return response.send content
         prestreamer = @upstreamAsync "prepushing", =>


syntax convs

syntax convs

--- library/nucleus/restful.coffee
+++ library/nucleus/restful.coffee
@@ -104,7 +104,7 @@
     # by this service of the internals that are comprising service.
     # Can be used from the outside, but generally should not be done.
     unsupported: (request, response, next) ->
-        methodNotAllowed = 405
+        assert methodNotAllowed = 405
         assert codes = http.STATUS_CODES
         assert message = codes[methodNotAllowed]
         doesJson = response.accepts(/json/) or no
@@ -161,18 +161,17 @@
     # you can supply the failing code and an HTTP response phrase.
     # Please use this methods rather than sending errors directly!
     reject: (response, content, code, phrase) ->
-        isContent = _.isObject content
-        noContent = "content has to be object"
-        throw new Error noContent unless isContent
-        @emit "reject", this, response, content
-        code = 400 unless _.isNumber code
-        phrase = phrase or STATUS_CODES[code]
+        noContent = "content has to be an object"
+        assert _.isObject(content or 0), noContent
+        try @emit "reject", this, response, content
+        code = 400 unless _.isNumber(code or null)
+        assert phrase = phrase or STATUS_CODES[code]
         uploader = -> response.send errors: content
         prestreamer = @upstreamAsync "prerejection", =>
-            response.writeHead(code, phrase); uploader()
+            response.writeHead code, phrase; uploader()
             poststreamer = @upstreamAsync "postrejection"
-            poststreamer response, content
-        prestreamer response, content
+            return poststreamer response, content
+        return prestreamer response, content

     # Push the supplied content to the requester by utilizing the
     # response object. This is effectively the same as calling the
@@ -188,4 +187,4 @@
         prestreamer = @upstreamAsync "prepushing", =>
             poststreamer = @upstreamAsync "postpushing"
             uploader(); poststreamer response, content
-        prestreamer response, content
+        return prestreamer response, content


@markijbema thinks this is better

@markijbema thinks this is better

--- core/app/backbone/views/top_channel_view.coffee
+++ core/app/backbone/views/top_channel_view.coffee
@@ -25,5 +25,5 @@
       @$(".no-channels").hide()
       @$(".top-channels").show()

-  onRender: ->
+  onCompositeCollectionRendered: ->
     @$(".top-channels-show-more").hide() if @collection.length < 6


Fixed bug causing resolve() to be called on an undefined deferred object when completing the import.

--- src/EntityImporter.coffee
+++ src/EntityImporter.coffee
@@ -473,26 +473,30 @@
       importId = args.importId
       df = null
       if importId?
+        Logger.info('Importing asset for import ID', importId)
         if importRequestMap[importId]
-          throw new Meteor.Error(500, 'Import request with ID ' + importId + ' already exists')
+          throw new Meteor.Error 500, "Import request with ID #{importId} already exists"
         df = importRequestMap[importId] = Q.defer()
         # Removed to prevent memory leaks.
         df.promise.fin -> delete importRequestMap[importId]
-        Logger.info('Import request started', importId)
+        Logger.info 'Import request started', importId
       Promises.runSync ->
         promise = EntityImporter.fromAsset(args)
-        promise.then(df.resolve, df.reject)
-        # Cancelling the import should return the import method.
-        df.promise
+        if df?
+          df.resolve(promise)
+          # Cancelling the import should return the import method.
+          df.promise
+        else
+          promise

     'entities/from/asset/cancel': (args) ->
       importId = args.importId
-      Logger.info('Cancelling import request...', importId)
+      Logger.info 'Cancelling import request...', importId
       @unblock()
       df = importRequestMap[importId]
       if df
         df.reject('Import cancelled')
-        Logger.info('Import request cancelled', importId)
+        Logger.info 'Import request cancelled', importId
       else
-        Logger.warn('Import request cannot be cancelled', importId)
+        Logger.warn 'No import request exists with the ID', importId


wrapped the sort select element in a label.

--- public/views/sort_view.coffee
+++ public/views/sort_view.coffee
@@ -3,14 +3,14 @@
   initialize: ->
     @sort = @direction = null

-  tagName: "div"
+  tagName: "label"

   events:
-    "change": "onChange"
+    "change select": "onChange"

   template: _.template """
     Sort by:
-    <select name="sort">
+    <select>
       <option value="">Name</option>
       <option value="price,asc">Price: low to high</option>
       <option value="price,desc">Price: high to low</option>


resurrection.activity: prevent feed population if query is equal to last one

--- client/Social/Activity/AppController.coffee
+++ client/Social/Activity/AppController.coffee
@@ -46,6 +46,7 @@
     @mainController        = KD.getSingleton 'mainController'
     @lastTo                = null
     @lastFrom              = Date.now()
+    @lastQuery             = null

     # if @mainController.appIsReady then @putListeners()
     # else @mainController.on 'AppIsReady', => @putListeners()
@@ -129,15 +130,20 @@
     # log "------------------ bindingsCleared", dateFormat(@lastFrom, "mmmm dS HH:mm:ss"), @_e

   handleQuery:(query = {})->
-
     if query.tagged
+      return  if @lastQuery and query.tagged is @lastQuery.tagged
       tag = KD.utils.slugify KD.utils.stripTags query.tagged
       @setWarning {text:tag, loading:yes}
       options = filterByTag: tag
     else if query.q
+      return  if @lastQuery and query.q is @lastQuery.q
       query = KD.utils.stripTags query.q
       @setWarning {text:query, loading:yes, type:"search"}
       options = searchText: query
+    else
+      return  if @lastQuery and Object.keys(query).length is 0 and Object.keys(@lastQuery).length is 0
+
+    @lastQuery = query

     # TODO: populateActivity will fire twice if there is a query (FIXME) C.T.
     @ready => @populateActivity options


Client: Terminal: Added a check for is terminal.server exist

--- client/app/lib/terminal/inputhandler.coffee
+++ client/app/lib/terminal/inputhandler.coffee
@@ -42,7 +42,12 @@
     @previousMouseY = -1

+  isTerminalReady: -> @terminal.server?
+
+
   keyDown: (event) ->
+
+    return unless @isTerminalReady()

     @terminal.scrollToBottom()
     @terminal.cursor.resetBlink()
@@ -57,7 +62,7 @@
     if Array.isArray seq
       seq = seq[if @applicationKeypad then 1 else 0]

-    if seq? and @terminal.server?
+    if seq?
       @terminal.server.controlSequence seq
       event.preventDefault()


Small modification to registration email.

--- client/tournaments/register/register.coffee
+++ client/tournaments/register/register.coffee
@@ -12,14 +12,14 @@
     if team.teamId is tId then return team
   Session.set 'active-team', activeTeam

-sendNotificationEmail = (tournament, role) ->
+sendNotificationEmail = (tournament, role, team) ->
   fullName = Meteor.user().profile.fullName
   Meteor.call 'sendEmail',
     from: 'All-Court Registrations <postmaster@allcourt.co.nz>',
     to: [Meteor.user().profile.email, 'Tennis Auckland <volunteers@tennisauckland.co.nz>'],
     replyTo: [Meteor.user().profile.email, 'Tennis Auckland <volunteers@tennisauckland.co.nz>'],
     subject: 'New user registration on allcourt.co.nz',
-    text: "#{fullName}, thank you for your registering as a #{role} at #{tournament}.\n\nIf you have any questions, just reply to this message and someone from Tennis Auckland will get back to you as soon as they can.\n\nAll-Court is still under development, so we appreciate your patience, but please let us know if you run into anything unexpected.\n\nWarm regards,\nTennis Auckland"
+    text: "#{fullName}, thank you for registering as a #{role} (#{team}) at #{tournament}.\n\nIf you have any questions, just reply to this message and someone from Tennis Auckland will get back to you as soon as they can.\n\nAll-Court is still under development, so we appreciate your patience, but please let us know if you run into anything unexpected.\n\nWarm regards,\nTennis Auckland"

 setAcceptedShifts = ->
   signupId = Session.get 'reg-id'
@@ -32,6 +32,7 @@
   roleName = Session.get('active-role').roleName
   tournamentName = Session.get('active-tournament').tournamentName
   teamId = Session.get('active-team').teamId
+  teamName = Session.get('active-team').teamName
   signup = Registrants.findOne { tournamentId: tId, userId: userId }
   Registrants.insert {
     userId: userId,
@@ -42,7 +43,7 @@
     unless err
       Session.set 'reg-id', id
       setAcceptedShifts()
-      sendNotificationEmail tournamentName, roleName
+      sendNotificationEmail tournamentName, roleName, teamName
       Template.userMessages.showMessage
         type: 'info'
         title: 'Success:'


whitespace cleanup

whitespace cleanup

--- src/adapters/shell.coffee
+++ src/adapters/shell.coffee
@@ -94,8 +94,5 @@
       else
         callback([])

-
-
-
 exports.use = (robot) ->
   new Shell robot


Reset cursor buffer position before each case sensitivity test

--- spec/motions-spec.coffee
+++ spec/motions-spec.coffee
@@ -953,6 +953,7 @@
       describe "case sensitivity", ->
         beforeEach ->
           editor.setText("\nabc\nABC\n")
+          editor.setCursorBufferPosition([0, 0])
           keydown('/')

         it "works in case sensitive mode", ->


Pop scope from the top of scopeStarts directly

--- src/token-iterator.coffee
+++ src/token-iterator.coffee
@@ -32,7 +32,14 @@
       tag = tags[@index]
       if tag < 0
         if tag % 2 is 0
-          @scopeEnds.push(atom.grammars.scopeForId(tag + 1))
+          scopeName = atom.grammars.scopeForId(tag + 1)
+          if @scopeEnds.length is 0
+            @scopeEnds.push(scopeName)
+          else
+            startsTop = this.scopeStarts.pop()
+            if startsTop isnt scopeName
+              @scopeStarts.push(startsTop)
+              @scopeEnds.push(scopeName)
           @scopes.pop()
         else
           scope = atom.grammars.scopeForId(tag)


Fix URL for download-cached service

Fix URL for download-cached service

--- resources/assets/scripts/app/services/download-cached.coffee
+++ resources/assets/scripts/app/services/download-cached.coffee
@@ -18,7 +18,7 @@
   '$rootScope', '$http', '$log'
   ($rootScope, $http, $log) ->
     download = (type, id, format) ->
-      url = '/api/web/' + type + '/download-cached/' + id + '/' + format
+      url = '/api/web/' + type + '/cached/' + id + '/' + format

       encodingComplete = (response) ->
         if response.data.url == null


Linter settings

Linter settings

--- atom/config.cson
+++ atom/config.cson
@@ -30,6 +30,9 @@
     general: {}
   "one-dark-ui":
     layoutMode: "Compact"
+  "linter-ui-default":
+    showPanel: true
+    statusBarRepresents: "Current File"
   "platformio-ide-terminal":
     core: {}
     style:


Revert "Re-enable pending pane items for easier tree-view use"

This reverts commit dc82cd28a0a43d44a1cc25d7941035eb07a71ca4.

--- atom/config.cson
+++ atom/config.cson
@@ -10,6 +10,7 @@
     includeCompletionsFromAllBuffers: false
     maxVisibleSuggestions: 5
   core:
+    allowPendingPaneItems: false
     closeEmptyWindows: false
     customFileTypes:
       "text.xml.plist": [


Modernize command definition syntax in init.coffee

--- atom/init.coffee
+++ atom/init.coffee
@@ -13,11 +13,11 @@
 # - Define tiny commands without the overhead of creating an entire package.
 # - Define commands for your own private use (e.g., commands that you might not
 #   want to share for some reason).
-atom.workspaceView.command 'dot-atom:demo', ->
+atom.commands.add 'atom-workspace', 'dot-atom:demo', ->
   console.log "Hello from dot-atom:demo"

 # Toggle between light and dark syntax theme.
-atom.workspaceView.command 'dot-atom:toggle-theme', ->
+atom.commands.add 'atom-workspace', 'dot-atom:toggle-theme', ->
   lightTheme = "solarized-light-syntax"
   darkTheme  = "solarized-dark-syntax"

@@ -38,75 +38,78 @@
 #
 # "Shut down all the [panes] on the detention level! ... Shut them all down.
 # Hurry!"
-atom.workspaceView.command 'dot-atom:close-all-panes', ->
+atom.commands.add 'atom-workspace', 'dot-atom:close-all-panes', ->
   atom.workspace.getPanes().forEach (pane) ->
     pane.destroyItems()

 # Define commands that should only be available in an editor view (and should
 # not be available in a tree view, for example).
-atom.workspaceView.eachEditorView (editorView) ->
+atom.commands.add 'atom-text-editor',

   # Approximate Vim's "H" motion: Move the cursor to the topmost line that is
   # currently visible.
-  editorView.command 'dot-atom:move-to-top-visible-line', ->
-    topRow = editorView.getFirstVisibleScreenRow()
+  'dot-atom:move-to-top-visible-line': ->
+    editor = atom.workspace.getActiveTextEditor()
+    topRow = editor.getFirstVisibleScreenRow()
     topRow = topRow + 1 unless topRow == 0
-    editorView.getEditor().setCursorBufferPosition [topRow, 0], autoscroll: false
+    editor.setCursorBufferPosition [topRow, 0], autoscroll: false

   # Approximate Vim's "M" motion: Move the cursor to the middle line that is
   # currently visible.
-  editorView.command 'dot-atom:move-to-middle-visible-line', ->
-    topRow = editorView.getFirstVisibleScreenRow()
-    bottomRow = editorView.getLastVisibleScreenRow()
+  'dot-atom:move-to-middle-visible-line': ->
+    editor = atom.workspace.getActiveTextEditor()
+    topRow = editor.getFirstVisibleScreenRow()
+    bottomRow = editor.getLastVisibleScreenRow()
     middleRow = Math.floor((bottomRow - topRow) / 2) + topRow
-    editorView.getEditor().setCursorBufferPosition [middleRow, 0]
+    editor.setCursorBufferPosition [middleRow, 0]

   # Approximate Vim's "L" motion: Move the cursor to the bottommost line that
   # is currently visible.
-  editorView.command 'dot-atom:move-to-bottom-visible-line', ->
-    editor = editorView.getEditor()
-    bottomRow = editorView.getLastVisibleScreenRow()
+  'dot-atom:move-to-bottom-visible-line': ->
+    editor = atom.workspace.getActiveTextEditor()
+    bottomRow = editor.getLastVisibleScreenRow()
     bottomRow = bottomRow - 1 unless bottomRow == editor.getLastScreenRow()
     editor.setCursorBufferPosition [bottomRow, 0], autoscroll: false

   # Approximate Vim's "zt" motion: Scroll the view such that the line containing
   # the cursor is at the top of the view.
-  editorView.command 'dot-atom:scroll-cursor-to-top', ->
-    editor = editorView.getEditor()
+  'dot-atom:scroll-cursor-to-top': ->
+    editor = atom.workspace.getActiveTextEditor()
     pixelPositionForCursorPosition =
-      editorView.pixelPositionForScreenPosition(editor.getCursorScreenPosition())
-    editorView.scrollTop pixelPositionForCursorPosition.top
+      editor.pixelPositionForScreenPosition(editor.getCursorScreenPosition())
+    editor.setScrollTop(pixelPositionForCursorPosition.top)

   # Approximate Vim's "zz" motion: Scroll the view such that the line containing
   # the cursor is vertically centered in the view.
-  editorView.command 'dot-atom:scroll-cursor-to-center', ->
-    editor = editorView.getEditor()
+  'dot-atom:scroll-cursor-to-center': ->
+    editor = atom.workspace.getActiveTextEditor()
     # TODO Why can't we implement this command using: `editorView.scrollToScreenPosition editorView.getCursorScreenPosition(), center: true`
     #      This line seems to be the culprit: https://github.com/atom/atom/blob/9fce6a2f1cea680d69bc3181774bc98c541f00b7/src/editor-view.coffee#L1133
     #      Because of that line, if the cursor is currently visible, no
     #      scrolling occurs.
     pixelPositionForCursorPosition =
-      editorView.pixelPositionForScreenPosition(editor.getCursorScreenPosition())
-    halfScreenHeight = editorView.scrollView.height() / 2
-    editorView.scrollTop(pixelPositionForCursorPosition.top - halfScreenHeight)
+      editor.pixelPositionForScreenPosition(editor.getCursorScreenPosition())
+    halfScreenHeight = editor.getHeight() / 2
+    editor.setScrollTop(pixelPositionForCursorPosition.top - halfScreenHeight)

   # Approximate Vim's "zb" motion: Scroll the view such that the line containing
   # the cursor is at the bottom of the view.
-  editorView.command 'dot-atom:scroll-cursor-to-bottom', ->
-    cursorPosition = editorView.getEditor().getCursorScreenPosition()
+  'dot-atom:scroll-cursor-to-bottom': ->
+    editor = atom.workspace.getActiveTextEditor()
+    cursorPosition = editor.getCursorScreenPosition()
     scrollBottomPosition = [cursorPosition.row + 1, cursorPosition.column]
     pixelPositionForScrollBottomPosition =
-      editorView.pixelPositionForScreenPosition(scrollBottomPosition)
-    editorView.scrollBottom pixelPositionForScrollBottomPosition.top
+      editor.pixelPositionForScreenPosition(scrollBottomPosition)
+    editor.setScrollBottom(pixelPositionForScrollBottomPosition.top)

   # Move the cursor to the next diff in the editor, and then scroll the view
   # such that the line containing the cursor is vertically centered in the view.
-  editorView.command 'dot-atom:center-next-diff', ->
+  'dot-atom:center-next-diff': ->
     editorView.trigger('git-diff:move-to-next-diff')
     editorView.trigger('dot-atom:scroll-cursor-to-center')

   # Move the cursor to the previous diff in the editor, and then scroll the view
   # such that the line containing the cursor is vertically centered in the view.
-  editorView.command 'dot-atom:center-previous-diff', ->
+  'dot-atom:center-previous-diff', ->
     editorView.trigger('git-diff:move-to-previous-diff')
     editorView.trigger('dot-atom:scroll-cursor-to-center')


Listen for errors in `utils/readBody`

Listen for errors in `utils/readBody`

--- src/utils/readBody.coffee
+++ src/utils/readBody.coffee
@@ -7,7 +7,7 @@
 module.exports = (req, options = {}) ->
   return Promise.resolve() if req.body

-  {promise, resolve} = Promise.defer()
+  {promise, resolve, reject} = Promise.defer()

   chunks = []
   length = 0
@@ -36,6 +36,8 @@
     then parseJSON resolve
     else resolve req.body

+  req.on "error", reject
+
   return promise

 #


Fix deprecation strings

Fix deprecation strings

--- src/keymap-manager.coffee
+++ src/keymap-manager.coffee
@@ -569,12 +569,12 @@
   keystrokeForKeyboardEvent: (event) ->
     keystrokeForKeyboardEvent(event)

-  # Deprecated: Use {::addKeymap} instead.
+  # Deprecated: Use {::add} instead.
   addKeymap: (source, bindings) ->
     # Grim.deprecate("Use KeymapManager::add instead.")
     @add(source, bindings)

-  # Deprecated: Use {::removeKeymap} instead.
+  # Deprecated: Use {::remove} instead.
   removeKeymap: (source) ->
     # Grim.deprecate("Use KeymapManager::remove instead.")
     @remove(source)


fixed #222 by making operations more flexible so that they can be called as standard variables

It's worth noting that most will do nothing. I want to rework this a bit
better in the future but this is a quick fix for now as I'm tempted to
rewrite this entire project since it's aged a lot and is currently
difficult to maintain.

--- src/lib/background.coffee
+++ src/lib/background.coffee
@@ -650,7 +650,9 @@
 # Avoid repetitive calls to render the text contents of a Mustache section by passed `callback` the
 # pre-rendered text and safely handling bad returns.
 rendered = (callback) ->
-  -> (text, render) -> callback(render text) ? ''
+  -> (text, render) ->
+    result = if arguments.length then callback(render text) else do callback
+    result ? ''

 # Attempt to select a tab in the current window displaying a page whose location begins with
 # `url`.
@@ -1029,14 +1031,14 @@
     datetime:              rendered (text) ->
       new Date().format if text then text
     decode:                rendered (text) ->
-      decodeURIComponent text
+      if text then decodeURIComponent text
     depth:                 screen.colorDepth
     # Deprecated since 1.0.0, use `linkstarget` instead.
     doanchortarget:        -> @linkstarget
     # Deprecated since 1.0.0, use `linkstitle` instead.
     doanchortitle:         -> @linkstitle
     encode:                rendered (text) ->
-      encodeURIComponent text
+      if text then encodeURIComponent text
     # Deprecated since 0.1.0.2, use `encode` instead.
     encoded:               ->
       encodeURIComponent @url
@@ -1044,10 +1046,10 @@
       _.escape text
     favicon:               ctab.favIconUrl
     fparam:                rendered (text) ->
-      url.fparam text
+      if text then url.fparam text
     fparams:               nullIfEmpty url.fparam()
     fsegment:              rendered (text) ->
-      url.fsegment parseInt text, 10
+      if text then url.fsegment parseInt text, 10
     fsegments:             url.fsegment()
     googl:                 googl.enabled
     googlaccount:          ->
@@ -1056,13 +1058,13 @@
     googloauth:            -> do @googlaccount
     java:                  navigator.javaEnabled()
     length:                rendered (text) ->
-      text.length
+      text?.length
     linkmarkdown:          ->
       toMarkdown @linkhtml
     linkstarget:           links.target
     linkstitle:            links.title
     lowercase:             rendered (text) ->
-      text.toLowerCase()
+      text?.toLowerCase()
     markdown:              ->
       toMarkdown getFromPage @html, 'html'
     markdowninline:        markdown.inline
@@ -1079,14 +1081,14 @@
     originalurl:           tab.url
     os:                    operatingSystem
     param:                 rendered (text) ->
-      url.param text
+      if text then url.param text
     params:                nullIfEmpty url.param()
     plugins:               _.chain(navigator.plugins).pluck('name').uniq().value()
     popular:               $.extend yes, {}, _.findWhere ext.templates, key: stats.popular
     screenheight:          screen.height
     screenwidth:           screen.width
     segment:               rendered (text) ->
-      url.segment parseInt text, 10
+      if text then url.segment parseInt text, 10
     segments:              url.segment()
     select:                ->
       getCallback 'select'
@@ -1111,7 +1113,7 @@
     text:                  ->
       getFromPage @html, 'text'
     tidy:                  rendered (text) ->
-      text.replace(/([ \t]+)/g, ' ').trim()
+      if text then text.replace(/([ \t]+)/g, ' ').trim()
     title:                 ctab.title or url.attr 'source'
     toolbarclose:          toolbar.close
     # Deprecated since 1.0.0, use the inverse of `toolbarpopup` instead.
@@ -1126,18 +1128,20 @@
     # Obsolete since 1.1.0, functionality has been removed.
     toolbarstyle:          no
     trim:                  rendered (text) ->
-      text.trim()
+      text?.trim()
     trimleft:              rendered (text) ->
-      text.trimLeft()
+      text?.trimLeft()
     trimright:             rendered (text) ->
-      text.trimRight()
+      text?.trimRight()
     unescape:              rendered (text) ->
       _.unescape text
     uppercase:             rendered (text) ->
-      text.toUpperCase()
+      text?.toUpperCase()
     url:                   url.attr 'source'
     version:               ext.version
     wordcount:             rendered (text) ->
+      return unless text
+
       # Oddly, this is the optimal method to calculate the word count on WebKit browsers.
       count   = 0
       text    = text.trim()