payment: fix view partials in form

payment: fix view partials in form

--- client/Main/payment/form.coffee
+++ client/Main/payment/form.coffee
@@ -51,7 +51,7 @@
     @existingCreditCardMessage = new KDCustomHTMLView
       cssClass : 'existing-cc-msg'
       partial  : '
-        We will use the credit card saved on your account for this purchase.
+        We will use the payment method saved on your account for this purchase.
       '

     @successMessage = new KDCustomHTMLView
@@ -73,14 +73,14 @@

     @yearPriceMessage = new KDCustomHTMLView
       cssClass  : 'year-price-msg'
-      partial   : "(You will be billed $#{yearPrice} for 12 months)"
+      partial   : "You will be billed $#{yearPrice} for 12 months"

     @securityNote = new KDCustomHTMLView
       cssClass  : 'security-note'
-      partial   : "
+      partial   : '
         Secure credit card payments
         Koding.com uses 128 Bit SSL Encrypted Transactions
-      "
+      '

     # for some cases, we need to show/hide
     # some of the subviews.
@@ -134,8 +134,8 @@
           type: 'submit'
           domId: 'paypal-submit'
           style: 'solid medium green submit-btn paypal'
-          title: 'Checkout using Paypal'
-          loader: yes
+          title: 'CHECKOUT USING

'
+          loader: {color: '#a7a7a7'}
           callback: => @emit 'PaypalButtonClicked'

@@ -198,7 +198,10 @@
       @successMessage.updatePartial "
         Depending on the plan upgraded to, you now have access to more computing
         and storage resources.
-        Learn more
+
+         Learn more
+
         about how to use your new resources.
       "
       @successMessage.show()


builder: improve readability

builder: improve readability

--- client/builder/lib/index.coffee
+++ client/builder/lib/index.coffee
@@ -123,17 +123,17 @@

         @_time chalk.blue 'manifests'

-        files.forEach (x) =>
-          @_time 'manifests: parse ' + x
+        files.forEach (file) =>
+          @_time 'manifests: parse ' + file

           parse = JSONStream.parse()

-          s = fs.createReadStream x
+          s = fs.createReadStream file
           rsManifests.push s

           tr = through.obj (row, enc, cb) =>
-            @_timeEnd 'manifests: parse ' + x
-            row.basedir = defined row.basedir, path.dirname @_resolve(x)
+            @_timeEnd 'manifests: parse ' + file
+            row.basedir = defined row.basedir, path.dirname @_resolve(file)
             manifests.push row
             cb null
           , (cb) =>
@@ -186,24 +186,24 @@
     outfile    = opts.jsOutfile
     sourcemaps = opts.sourcemapsOutfile

-    rewriteMap = appNames.reduce (acc, x) ->
-      acc[x] = './' + x + '/lib/'
+    rewriteMap = appNames.reduce (acc, appName) ->
+      acc[appName] = "./#{appName}/lib/"
       return acc
     , {}

-    modules = manifests.map (x) =>
-      name = x.name
+    modules = manifests.map (manifest) =>
+      name = manifest.name
       if name is 'ide'
-        name = 'IDE'
+        name = manifest.name.toUpperCase()
       else
         name = name.charAt(0).toUpperCase() + name.slice(1)

       return {
-        identifier : x.name
+        identifier : manifest.name
         name       : name
-        routes     : x.routes
-        shortcuts  : x.shortcuts
-        style      : opts.baseurl + '/' + x.name + '.css'
+        routes     : manifest.routes
+        shortcuts  : manifest.shortcuts
+        style      : opts.baseurl + '/' + manifest.name + '.css'
       }

     transforms = [ coffeeify, pistachioify ]
@@ -249,8 +249,8 @@
     if opts.watchJs
       b = bant.watch opts_
       b.on 'update', (ids) ->
-        ids.forEach (x) ->
-          console.log 'updated ' + x
+        ids.forEach (id) ->
+          console.log 'updated ' + id
         bundle()
     else
       b = bant opts_
@@ -333,7 +333,7 @@
     start   = 0

-    copyKd = (cb) ->
+    copyKd = (callback) ->
       kdPath = path.dirname require.resolve STYLES_KDJS_MODULE_NAME
       kdPath = kdPath.match(new RegExp('(.+)/'+STYLES_KDJS_MODULE_NAME))[1]
       kdCssFile = path.join kdPath, STYLES_KDJS_MODULE_NAME, STYLES_KDJS_CSS_FILE
@@ -354,17 +354,16 @@
         console.log msg
         if opts.watchJs
           notify 'styles', msg
-        if cb
-          cb()
+
+        callback?()

       if opts.watchJs and not watchingKd
         watchingKd = true
         w = chokidar.watch kdCssFile, persistent: yes
-        w.on 'change', ->
-          copyKd()
-
-
-    styl = (x, globs) ->
+        w.on 'change', copyKd
+
+
+    styl = (manifest, globs) ->
       pending += 1

       opts_ =
@@ -374,7 +373,7 @@
         sourcemap :
           inline: if opts.debugCss then yes else no

-      basename = x.name + '.css'
+      basename = manifest.name + '.css'
       outfile = path.join opts.stylesOutdir, basename

       time 'styles: write ' + outfile
@@ -398,15 +397,15 @@
             notify 'styles', msg

-    bundle = (x)->
+    bundle = (manifest)->
       start = Date.now()

-      x.styles.forEach (basename) ->
-        dir = path.join x.basedir, basename
+      manifest.styles.forEach (basename) ->
+        dir = path.join manifest.basedir, basename
         globs = [ path.join dir, '**', '*.' + STYLES_EXTENSION ]
         globs.push "!#{commons}"

-        styl x, globs
+        styl manifest, globs

         if opts.watchCss
           w = chokidar.watch dir, persistent: yes
@@ -415,14 +414,14 @@
               if e is 'modified' or e is 'deleted'
                 console.log e + ' ' + file
                 start = Date.now()
-                styl x, globs
+                styl manifest, globs

     init = =>
       fn = ->
-        manifests.forEach (x) ->
-          return  unless Array.isArray x.styles
-          bundle x
+        manifests.forEach (manifest) ->
+          return  unless Array.isArray manifest.styles
+          bundle manifest
       @on '_updated-sprites', -> fn()
       copyKd -> fn()

@@ -462,12 +461,12 @@
     timeEnd = @_timeEnd.bind this

     getFiles [ '**/*' ], opts.dir, (err, files) ->
-      files = files.filter (x) ->
-        return true  unless x is 'Readme.md'
+      files = files.filter (basename) ->
+        return true  unless basename is 'Readme.md'

       pending = files.length

-      entities = files.map (basename) ->
+      rows = files.map (basename) ->
         file = path.join opts.dir, basename
         outfile = path.join opts.outdir, basename
         outdir = path.dirname outfile
@@ -477,19 +476,19 @@
           outdir: outdir
         }

-      dirs = entities.map (x) ->
-        return x.outdir
+      dirs = rows.map (row) ->
+        return row.outdir

       dirs = nub dirs

       createDirs dirs, ->
-        entities.forEach (x) ->
-          time opts.label + ': copy ' + x.outfile
-          s = fs.createReadStream x.file
-          ws = fs.createWriteStream x.outfile
+        rows.forEach (row) ->
+          time opts.label + ': copy ' + row.outfile
+          s = fs.createReadStream row.file
+          ws = fs.createWriteStream row.outfile
           s.pipe ws
           ws.once 'finish', ->
-            timeEnd opts.label + ': copy ' + x.outfile
+            timeEnd opts.label + ': copy ' + row.outfile
             if --pending is 0 then done()

@@ -533,9 +532,9 @@

         s = vfs.src(dir).pipe spritesmith opts_
         return {
-          name: manifest.name
-          rname: rname
-          stream: s
+          name   : manifest.name
+          rname  : rname
+          stream : s
         }

       entities.forEach (entity) ->
@@ -563,12 +562,12 @@
           end_()

-    bundle = (x) ->
+    bundle = (manifest) ->
       start = Date.now()

-      x.sprites.forEach (basename) ->
-        dir = path.join x.basedir, basename
-        smith x, dir
+      manifest.sprites.forEach (basename) ->
+        dir = path.join manifest.basedir, basename
+        smith manifest, dir

         if opts.watchSprites
           w = chokidar.watch dir, persistent: yes
@@ -577,12 +576,12 @@
               if e is 'modified' or e is 'deleted'
                 console.log e + ' ' + file
                 start = Date.now()
-                smith x, dir
-
-
-    manifests.forEach (x) ->
-      return  unless Array.isArray x.sprites
-      bundle x
+                smith manifest, dir
+
+
+    manifests.forEach (manifest) ->
+      return  unless Array.isArray manifest.sprites
+      bundle manifest

   _time: (msg) ->
@@ -638,8 +637,8 @@

 createDirs = (dirs, cb) ->
-  fns = dirs.map (x) ->
-    return mkdirp.bind mkdirp, x
+  fns = dirs.map (dir) ->
+    return mkdirp.bind mkdirp, dir
   async.parallel fns, (err) ->
-    if err then throw err
+    throw err  if err
     cb null


Collaboration: Renew auth token after 55mins.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -768,6 +768,10 @@
       @listenChangeEvents()
       @rtm.isReady = yes

+      KD.utils.repeat 60 * 55, => @rtm.reauth()
+      @rtm.on 'ReauthSucceed', (token) =>
+        log 'reauth succeed, replace existing token with', token
+
   registerSessionId: ->
     collaborators = @rtm.getCollaborators()


AppController: `splitTabView` method now accepts options for ideView.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -115,10 +115,10 @@
   setActiveTabView: (tabView) ->
     @activeTabView = tabView

-  splitTabView: (type = 'vertical') ->
+  splitTabView: (type = 'vertical', ideViewOptions) ->
     ideView        = @activeTabView.parent
     ideParent      = ideView.parent
-    newIDEView     = new IDE.IDEView
+    newIDEView     = new IDE.IDEView ideViewOptions
     @activeTabView = null

     ideView.detach()


Fix DOM.appendHTML()

Fix DOM.appendHTML()

--- src/marbles/dom.js.coffee
+++ src/marbles/dom.js.coffee
@@ -78,8 +78,7 @@
         new_node.innerHTML = script_node.innerHTML
       DOM.replaceWith(script_node, new_node)

-    for node in tmp_el.childNodes
-      continue unless node
+    while node = tmp_el.firstChild
       el.appendChild(node)
     el


street view: only show when the hide is done

--- app/_src/components/office-street-view.coffee
+++ app/_src/components/office-street-view.coffee
@@ -32,7 +32,7 @@
 		if (@isDisabledOnTouch and (Modernizr.touch or navigator.msMaxTouchPoints))
 			@setFallback( @$context.data( DATA_FALLBACK ) )
 		else
-			@showSpinner()
+			@spinner.show()
 			window.onload = @appendMapScriptToBody
 			#google.maps.event.addDomListener(window, 'load', @appendMapScriptToBody)
 			@$body.on("pjax:done", @appendMapScriptToBody)
@@ -62,15 +62,16 @@
 			zoom: @$context.data('zoom')
 		}

-		@hideSpinner()
-		@pano = new google.maps.StreetViewPanorama(@context, @panoramaOptions)
-		@pano.setVisible(true)
+		@spinner.hide(=>
+			@pano = new google.maps.StreetViewPanorama(@context, @panoramaOptions)
+			@pano.setVisible(true)

-		setTimeout(=>
-		  @$context.addClass( CLASS_MAP_LOADED )
-		, LOADED_CLASS_TIMEOUT)
+			setTimeout(=>
+			  @$context.addClass( CLASS_MAP_LOADED )
+			, LOADED_CLASS_TIMEOUT)

-		google.maps.event.trigger(@context, 'resize');
+			google.maps.event.trigger(@context, 'resize');
+		)

 	destroy: =>
 		google.maps.event.clearInstanceListeners(window)
@@ -83,9 +84,3 @@
 		@$context.attr('data-bg-src', url)
 		@$context.addClass( CLASSES_FALLBACK )
 		ResponsiveIO.refresh(@$context.get(0))
-
-	showSpinner: ->
-		@spinner.show()
-
-	hideSpinner: ->
-		@spinner.hide()


fix missing hash

fix missing hash

--- app/assets/javascripts/result-chart.js.coffee
+++ app/assets/javascripts/result-chart.js.coffee
@@ -14,7 +14,7 @@
     $resultsHtml.appendTo @container

   appendAggregatableResults: (resultObj)->
-    colors = ["#F33E6F", "#2D91EA", "F7464A", "#69D2E7", "#4D5360", "#F38630", "black"]
+    colors = ["#F33E6F", "#2D91EA", "#F7464A", "#69D2E7", "#4D5360", "#F38630", "black"]

     # convert results to chart.js data format
     # fetch all values, and assign a color


TOUCH: fix bug (propositions were not loaded because loadPropositions was not called)

--- app/assets/javascripts/touch/router.js.coffee
+++ app/assets/javascripts/touch/router.js.coffee
@@ -58,6 +58,7 @@
       @compareView = new CompareView(el: "#compare")
       @compareView.render()
       view = new PropositionsView(el: "#compare .table-view")
+      view.loadPropositions()
       view.render()

     # set candidacies, tag using url


adding sub router support

adding sub router support

--- src/mixins/routing.coffee
+++ src/mixins/routing.coffee
@@ -8,7 +8,6 @@
     - routing:opening -- sent to Backbone before a view is created
     - routing:closing -- sent to old view, before it is closed
   ###
-  _is_router: true

   # will prefix all routes
   prefix: null
@@ -19,7 +18,7 @@

   # a selector to determine which part of this view should be updated
   # when the router picks up a URL change
-  routingSelector: ".main"
+  routingSelector: null

   # how long switching a view should take
   transitionDuration: 150
@@ -27,6 +26,8 @@
   routes: {}

   initialize: (options={}) ->
+    @router = options.router
+
     if not Backbone.History.started
       @prefix = options.prefix or ""
       if "*" in @prefix
@@ -37,6 +38,7 @@

       for url in Object.keys(@routes).reverse()
         view = @routes[url]
+        callback = null

         # TODO: check if view is a backbone router

@@ -45,31 +47,44 @@

           # is Routing Mixin?
           if index.views.MixinView.listMixins(view).indexOf(Routing) > -1
-            @routers[url] = new view({router: @, prefix: prefix})
-
-          # normal class based view
-          opts = {router: @, viewClass: view, prefix: prefix}
-          console.log ">>>", opts.prefix
-          opts.namedParams = opts.prefix.match(/:([a-z\-\_]+)/gi)
-          callback = @update.bind(@, opts)
+            @routers[url] = new view
+              router: @,
+              prefix: prefix
+          else
+            # normal class based view
+            opts = {router: @, viewClass: view, prefix: prefix}
+            opts.namedParams = opts.prefix.match(/:([a-z\-\_]+)/gi)
+            callback = @update.bind(@, opts)

         else if _.isFunction view
           callback = view
-          console.log view

         else
-          console.log "Nothing", view
+          console.error "This view is not an view class or callback", view

-        console.log @prefix + url, url, callback
-        @_router.route(@prefix + url, callback)
-        console.log Backbone.history
-        # @routes[options.router] opts
+        if callback
+          @_router.route(@prefix + url, url, callback)
+
+  remove: () ->
+    console.log "detaching element"
+    $(@el).detach()

   update: (options={}) ->
-    console.log("updating", options.viewClass, options)
     reverse = options.reverse
+
+    # attach the router if isn't attached
+    if not document.body.contains @el
+      if @router
+        @router.update
+          view: @
+          reverse: reverse
+      else
+        new Exception(
+          "Don't know how you want to attach this router to the dom.
+          Usually it is rendered and attached by another view or the main app"
+        )
+
     old = @view
-
     if old
       # notify the old view that it is being transitioned out
       Backbone.trigger "routing:closing",
@@ -82,30 +97,42 @@
       # we extend the time a little so the GPU can catch up before removal
       _.delay (-> old.remove()), @transitionDuration + 100

-    # add in the view
-    args = (a for a in arguments)
-    options.args = args[1..args.length]
-    if options.namedParams
-      options.kwargs = _.object \
-        ([options.namedParams[i]?.substring(1), a] for i, a of options.args)
+    if options.viewClass
+      # add in the view
+      args = (a for a in arguments)
+      options.args = args[1..args.length]
+      if options.namedParams
+        options.kwargs = _.object \
+          ([options.namedParams[i]?.substring(1), a] for i, a of options.args)
+      else
+        options.kwargs = {}
+
+      @getRouteOptions(options)
+      view = new options.viewClass(options)
     else
-      options.kwargs = {}
+      view = options.view

-    @getRouteOptions(options)
-    @view = new options.viewClass(options)
-    $(@routingSelector).append(@view.render().el)
+    @getRoutingElement().append(view.render().el)

     # notify the world, a new view is coming in
     Backbone.trigger "routing:opened",
       router: @
       time: @transitionDuration
       reverse: options.reverse
-      previousView: old
+      previousView: @view
       options: options
-      view: @view
+      view: view
+
+    # now safe to set view
+    @view = view

   getRouteOptions: (options) ->
     return options
+
+  getRoutingElement: () ->
+    if @routingSelector
+      return $(@routingSelector)
+    return @$el

 module.exports =


Fix #1381

Fix #1381

--- client/views/app/message.coffee
+++ client/views/app/message.coffee
@@ -130,5 +130,5 @@
 				view.parentView.parentView.parentView.parentView.parentView.templateInstance?().atBottom = true
 			else
 				if view.parentView.parentView.parentView.parentView.parentView.templateInstance?().atBottom isnt true
-					newMessage = view.parentView.parentView.parentView.parentView.parentView.templateInstance().find(".new-message")
+					newMessage = view.parentView.parentView.parentView.parentView.parentView.templateInstance?()?.find(".new-message")
 					newMessage?.className = "new-message"


Kernel errors experimental mode

Kernel errors experimental mode

--- lib/kernel.coffee
+++ lib/kernel.coffee
@@ -55,15 +55,24 @@

         @kernelProcess.stdout.on 'data', (data) ->
             console.log "kernel process received on stdout:", data.toString()
-        @kernelProcess.stderr.on 'data', (data) ->
-            console.error "kernel process received on stderr:", data.toString()
-
+        @kernelProcess.stderr.on 'data', @stderr
             # console.log "launching:", commandString
             # @kernelProcess = child_process.exec commandString, (error, stdout, stderr) ->
             #     console.log 'stdout: ', stdout
             #     console.log 'stderr: ', stderr
             #     if error != null
             #         console.log 'exec error: ', error
+     stderr: (data, caption = "kernel answer") ->
+        detail = data.toString()
+        method = 'addInfo'
+        if /warning/gi.test detail
+            method = 'addWarning'
+        if /error/gi.test detail
+            method = 'addError'
+
+        console.error "kernel process received on stderr:", detail
+        atom.notifications[method] "Hydrogen: #{caption}",
+         detail: detail, dismissable: /error/i.test method

     connect: ->
         @shellSocket = zmq.socket 'dealer'
@@ -221,6 +230,8 @@

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

         if message.type == 'status'
             status = message.contents.execution_state


Start to process GET parameters

Start to process GET parameters

--- public/js/map.coffee
+++ public/js/map.coffee
@@ -1,4 +1,15 @@
 $(document).ready( () ->
+  param = _getParameters()
+  switch param.type
+    when "search"
+
+    when "directions"
+)
+
+###
+  Extracts GET parameters from url
+###
+_getParameters = () ->
   pl     = /\+/g  #Regex for replacing addition symbol with a space
   search = /([^&=]+)=?([^&]*)/g
   decode = (s) -> return decodeURIComponent(s.replace(pl, " "));
@@ -7,5 +18,4 @@
   urlParams = {};
   while (match = search.exec(query))
     urlParams[decode(match[1])] = decode(match[2]);
-  console.log(urlParams);
-)+  return urlParams;


Replace doubled slashes in redirect url

Replace doubled slashes in redirect url

--- src/podroller/core.coffee
+++ src/podroller/core.coffee
@@ -82,8 +82,8 @@
             if !req.param('uuid')
                 id = uuid.v4()
                 console.debug "Redirecting with UUID of #{ id }"
-                url = "#{ @options.redirect_url }#{req.originalUrl}" + (if Object.keys(req.query).length > 0 then "&uuid=#{id}" else "?uuid=#{id}")
-                res.redirect 302, url
+                url = "#{ @options.redirect_url }#{ req.originalUrl }" + (if Object.keys(req.query).length > 0 then "&uuid=#{id}" else "?uuid=#{id}")
+                res.redirect 302, url.replace('//','/')

             else
                 console.debug "Request UUID is #{ req.param('uuid') }"


Fixed method naming

Fixed method naming

--- lib/oauth1.coffee
+++ lib/oauth1.coffee
@@ -40,7 +40,7 @@
 	_setParams: (parameters) ->
 		@_params[k] = v for k,v of parameters

-	_replace_param: (param, params, hard_params, keyset) ->
+	_replaceParam: (param, params, hard_params, keyset) ->
 		param = param.replace /\{\{(.*?)\}\}/g, (match, val) ->
 			return hard_params[val] || ""
 		return param.replace /\{(.*?)\}/g, (match, val) ->
@@ -69,12 +69,12 @@
 		if typeof opts.options?.request_token == 'object'
 			query = opts.options.request_token
 		for name, value of request_token.query
-			param = @_replace_param value, @_params, state:state.id, callback:config.host_url+config.relbase, parameters
+			param = @_replaceParam value, @_params, state:state.id, callback:config.host_url+config.relbase, parameters
 			query[name] = param if param
 		headers = {}
 		headers["Accept"] = short_formats[request_token.format] || request_token.format if request_token.format
 		for name, value of request_token.headers
-			param = @_replace_param value, @_params, {}, parameters
+			param = @_replaceParam value, @_params, {}, parameters
 			headers[name] = param if param
 		options =
 			url: request_token.url
@@ -107,10 +107,10 @@
 		if typeof opts.options?.authorize == 'object'
 			query = opts.options.authorize
 		for name, value of authorize.query
-			param = @_replace_param value, @_params, state:state.id, callback:config.host_url+config.relbase, parameters
+			param = @_replaceParam value, @_params, state:state.id, callback:config.host_url+config.relbase, parameters
 			query[name] = param if param
 		query.oauth_token = responseParser.oauth_token
-		url = @_replace_param authorize.url, @_params, {}, parameters
+		url = @_replaceParam authorize.url, @_params, {}, parameters
 		url += "?" + querystring.stringify query
 		callback null, url

@@ -148,15 +148,15 @@
 			for extra in (provider.oauth1.authorize.extra||[])
 				hard_params[extra] = req.params[extra] if req.params[extra]
 			for name, value of access_token.query
-				param = @_replace_param value, @_params, hard_params, parameters
+				param = @_replaceParam value, @_params, hard_params, parameters
 				query[name] = param if param
 			headers = {}
 			headers["Accept"] = short_formats[access_token.format] || access_token.format if access_token.format
 			for name, value of access_token.headers
-				param = @_replace_param value, @_params, {}, parameters
+				param = @_replaceParam value, @_params, {}, parameters
 				headers[name] = param if param
 			options =
-				url: @_replace_param access_token.url, @_params, hard_params, parameters
+				url: @_replaceParam access_token.url, @_params, hard_params, parameters
 				method: access_token.method?.toUpperCase() || "POST"
 				oauth:
 					callback: query.oauth_callback
@@ -226,13 +226,13 @@
 			if options.url[0] != '/'
 				options.url = '/' + options.url
 			options.url = oauthrequest.url + options.url
-		options.url = @_replace_param options.url, @_params, parameters.oauthio, parameters
+		options.url = @_replaceParam options.url, @_params, parameters.oauthio, parameters

 		# build query
 		options.qs = {}
 		options.qs[name] = value for name, value of req.query
 		for name, value of oauthrequest.query
-			param = @_replace_param value, @_params, parameters.oauthio, parameters
+			param = @_replaceParam value, @_params, parameters.oauthio, parameters
 			options.qs[name] = param if param

 		options.oauth =
@@ -248,7 +248,7 @@
 			'accept-language':req.headers['accept-language']
 			'content-type':req.headers['content-type']
 		for name, value of oauthrequest.headers
-			param = @_replace_param value, @_params, parameters.oauthio, parameters
+			param = @_replaceParam value, @_params, parameters.oauthio, parameters
 			options.headers[name] = param if param

 		# build body


renamed variable in parseDSN

renamed variable in parseDSN

--- lib/sentry.coffee
+++ lib/sentry.coffee
@@ -8,13 +8,13 @@
 parseDSN = (dsn) =>
   parsed = nodeurl.parse(dsn)
   try
-    data =
+    credentials =
       project_id: parsed.path.split('/')[1]
       key: parsed.auth.split(':')[0]
       secret: parsed.auth.split(':')[1]
   catch err
-    data = {}
-  return data
+    credentials = {}
+  return credentials

 module.exports = class Sentry extends events.EventEmitter


added event for gauges

added event for gauges

--- src/scripts/gauges.coffee
+++ src/scripts/gauges.coffee
@@ -28,7 +28,6 @@
             title: g.title
             views: g.today.views
             people: g.today.people
-
         callback null, gauges

   getViewsForYesterday: (callback) ->
@@ -73,36 +72,46 @@
         else
           callback "Could not get gauges for today"

+
 module.exports = (robot) ->

   robot.respond /gauges for (today|yesterday)/i, (msg) ->
+    day = msg.match[1]
+    robot.emit "gauges", { user: msg.user, for: day }
+
+  robot.respond /gauges for (\d{4}-\d{2}-\d{2})/i, (msg) ->
+    day = msg.match[1]
+    robot.emit "gauges", { user: msg.user, for: 'data' , day: day }
+
+  # handle gauges events, can be emited by other script
+  robot.on "gauges", (data) ->
     gauges = new Gauges robot, process.env.HUBOT_GAUGES_TOKEN
-    day = msg.match[1]

-    switch day
+    #handle a list of gauges
+    handler = (err, list) ->
+      if err?
+        robot.send data.user, "#{err}"
+      else
+        for g in list
+          robot.send data.user, "#{g.title}: Views #{g.views} People #{g.people}"
+
+    switch data.for
       when "today"
-        gauges.getViewsForToday (err, gauges) ->
-          if err?
-            msg.send "#{err}"
-          else
-            for g in gauges
-              msg.send "#{g.title}: Views #{g.views}, People #{g.people}"
+        gauges.getViewsForToday handler

       when "yesterday"
-        gauges.getViewsForYesterday (err, gauges) ->
-          if err?
-            msg.send "#{err}"
-          else
-            for g in gauges
-              msg.send "#{g.title}: Views #{g.views} People #{g.people}"
+        gauges.getViewsForYesterday handler

-  robot.respond /gauges for (\d{4}-\d{2}-\d{2})/i, (msg) ->
-    gauges = new Gauges robot, process.env.HUBOT_GAUGES_TOKEN
-    day = msg.match[1]
+      when "data"
+          gauges.getViewsForDate data.day, handler

-    gauges.getViewsForDate day, (err, gauges) ->
-      if err?
-        msg.send "#{err}"
-      else
-        for g in gauges
-          msg.send "#{g.title}: Views #{g.views} People #{g.people}"
+
+
+
+
+
+
+
+
+
+


Started building out Shodan module, but the API is having issues

--- src/scripts/shodan.coffee
+++ src/scripts/shodan.coffee
@@ -13,6 +13,17 @@
 # Author:
 #   Scott J Roberts - @sroberts

+SHODAN_KEY = process.env.SHODAN_KEY
+
+shodan_url = "http://www.shodanhq.com/api/"
+
 module.exports = (robot) ->
   robot.respond /shodan (.*)/i, (msg) ->
     shodan_term = msg.match[1].toLowerCase()
+
+    robot.http(shodan_url + "/api/host?key=#{SHODAN_KEY}&ip=#{shodan_term}")
+      #.query('ip': shodan_term, 'key': SHODAN_KEY)
+      .get() (err, res, body) ->
+        msg.send body
+        msg.send res
+        msg.send err


Update src/server/session.coffee

Update src/server/session.coffee

callback is not defined, removing it

--- src/server/session.coffee
+++ src/server/session.coffee
@@ -66,8 +66,7 @@

       if error
         console.warn "Invalid query #{query} from #{agent.sessionId}: #{error}"
-        session.abort()
-        return callback()
+        return session.abort()

       # The agent can specify null as the docName to get a random doc name.
       if query.doc is null


Changed error reported to the user.

Changed error reported to the user.

--- lib/worker.coffee
+++ lib/worker.coffee
@@ -391,7 +391,7 @@
           undefined
       @waiting_for = null
       if err
-        @resume "#{@key} expected finished keys but got nulls: #{err.join ','}"
+        @resume "PLEASE CONTACT WATERFIELD ENERGY DEV TEAM ABOUT THIS ERROR\n#{@key} expected finished keys but got nulls: #{err.join ','}"
       else
         @resume null, arr


Properly format customFileTypes option in CSON

--- atom/config.cson
+++ atom/config.cson
@@ -12,7 +12,9 @@
   core:
     closeEmptyWindows: false
     customFileTypes:
-      'text.xml.plist': ['plist']
+      "text.xml.plist": [
+        "plist"
+      ]
     disabledPackages: [
       "archive-view"
       "autocomplete"


Atom version bump

Atom version bump

--- atom/config.cson
+++ atom/config.cson
@@ -18,11 +18,16 @@
       "tree-view"
     ]
   editor:
-    invisibles: {}
+    invisibles:
+      {}
   linter:
     lintDebug: true
     showInfoMessages: true
-  "fuzzy-finder": {}
-  tabs: {}
+  "fuzzy-finder":
+    {}
+  tabs:
+    {}
   "vim-mode":
     useClipboardAsDefaultRegister: true
+  "relative-line-numbers":
+    showNormalLineNumbers: false


Use four spaces for shell script files

Use four spaces for shell script files

--- atom/init.coffee
+++ atom/init.coffee
@@ -14,6 +14,8 @@
   Java:
     tabLength: 4
   Python:
+    tabLength: 4
+  'Shell Script (Bash)':
     tabLength: 4

 # Apply the settings to the editor session.


Add Atom command to toggle light/dark syntax themes

--- atom/init.coffee
+++ atom/init.coffee
@@ -28,6 +28,21 @@
 #   want to share for some reason).
 atom.workspaceView.command 'dot-atom:demo', ->
   console.log "Hello from dot-atom:demo"
+
+# Toggle between light and dark syntax theme.
+atom.workspaceView.command 'dot-atom:toggle-theme', ->
+  lightTheme = "solarized-light-syntax"
+  darkTheme  = "solarized-dark-syntax"
+
+  enabledthemes = atom.themes.getEnabledThemeNames();
+  if lightTheme in enabledthemes
+    index = enabledthemes.indexOf(lightTheme)
+    enabledthemes[index] = darkTheme
+  else
+    index = enabledthemes.indexOf(darkTheme)
+    enabledthemes[index] = lightTheme
+
+  atom.themes.setEnabledThemes(enabledthemes)

 # Close all panes.
 #


Add key binding for showing current file in Finder

--- atom/keymap.cson
+++ atom/keymap.cson
@@ -83,6 +83,9 @@
   # Opens inline URL in default web browser if cursor is within URL
   'alt-cmd-o': 'link:open'

+  # Open the active document in Finder
+  'shift-cmd-r': 'tree-view:show-current-file-in-file-manager'
+
 # Toggle match by whole word in Find and Replace / Project Find panels
 '.platform-darwin .find-and-replace':
   'alt-cmd-b': 'find-and-replace:toggle-whole-word-option'


Atom - Remove default header in keymap.cson so custom things are more readable

--- atom/keymap.cson
+++ atom/keymap.cson
@@ -1,35 +1,3 @@
-# Your keymap
-#
-# Atom keymaps work similarly to style sheets. Just as style sheets use
-# selectors to apply styles to elements, Atom keymaps use selectors to associate
-# keystrokes with events in specific contexts. Unlike style sheets however,
-# each selector can only be declared once.
-#
-# You can create a new keybinding in this file by typing "key" and then hitting
-# tab.
-#
-# Here's an example taken from Atom's built-in keymap:
-#
-# 'atom-text-editor':
-#   'enter': 'editor:newline'
-#
-# 'atom-workspace':
-#   'ctrl-shift-p': 'core:move-up'
-#   'ctrl-p': 'core:move-down'
-#
-# You can find more information about keymaps in these guides:
-# * http://flight-manual.atom.io/using-atom/sections/basic-customization/#customizing-keybindings
-# * http://flight-manual.atom.io/behind-atom/sections/keymaps-in-depth/
-#
-# If you're having trouble with your keybindings not working, try the
-# Keybinding Resolver: `Cmd+.` on macOS and `Ctrl+.` on other platforms. See the
-# Debugging Guide for more information:
-# * http://flight-manual.atom.io/hacking-atom/sections/debugging/#check-the-keybindings
-#
-# This file uses CoffeeScript Object Notation (CSON).
-# If you are unfamiliar with CSON, you can read more about it in the
-# Atom Flight Manual:
-# http://flight-manual.atom.io/using-atom/sections/basic-customization/#configuring-with-cson

 # NK Custom


Add options support to setCookie.

Add options support to setCookie.

--- coffee/chaplin/lib/utils.coffee
+++ coffee/chaplin/lib/utils.coffee
@@ -94,8 +94,20 @@

     # Set a session cookie

-    setCookie: (key, value) ->
-      document.cookie = key + '=' + encodeURIComponent(value)
+    setCookie: (key, value, options = {}) ->
+      payload = "#{encodeURIComponent(key)}=#{encodeURIComponent(value)}"
+      getOption = (name) ->
+        if options[name] then "; #{name}=#{options[name]}" else ''
+
+      expires = if options.expires
+        "; expires=#{options.expires.toUTCString()}"
+      else
+        ''
+
+      document.cookie = [
+        payload, expires,
+        (getOption 'path'), (getOption 'domain'), (getOption 'secure')
+      ].join('')

     expireCookie: (key) ->
       document.cookie = "#{key}=nil; expires=#{(new Date).toGMTString()}"


Removing some comments that will go into the Readme

--- coffee/number_helpers.js.coffee
+++ coffee/number_helpers.js.coffee
@@ -1,9 +1,5 @@
 class @NumberHelpers
   @number_to_currency = (float, opts={}) ->
-    # _precision - Sets the level of precision (defaults to 2).
-    # _unit - Sets the denomination of the currency (defaults to "$").
-    # _separator - Sets the separator between the units (defaults to ".").
-    # _delimiter - Sets the thousands delimiter (defaults to ",").
     _precision  = opts.precision ? 2
     _unit       = opts.unit ? '$'
     _separator  = opts.separator ? '.'


forgot it in there

forgot it in there

--- commands/version-control.coffee
+++ commands/version-control.coffee
@@ -38,8 +38,6 @@
                 respond stdout
             return

-console.log config
-
 brain.addPattern 'svn export',
     match: /^svn\sexport\s(.+)/i
     fn: (user, m, cb) -> svnExport m[1], cb


activity/flux: reformat getters

activity/flux: reformat getters

--- client/activity/lib/flux/getters.coffee
+++ client/activity/lib/flux/getters.coffee
@@ -261,18 +261,18 @@
   SuggestionsSelectedIndexStore
   calculateListSelectedIndex
 ]
-currentSuggestionsSelectedItem  = [
+currentSuggestionsSelectedItem = [
   SuggestionsStore
   currentSuggestionsSelectedIndex
   getListSelectedItem
 ]

-filteredEmojiListQuery         = (stateId) -> [
+filteredEmojiListQuery = (stateId) -> [
   FilteredEmojiListQueryStore
   (queries) -> queries.get stateId
 ]
 # Returns a list of emojis filtered by current query
-filteredEmojiList              = (stateId) -> [
+filteredEmojiList = (stateId) -> [
   EmojisStore
   filteredEmojiListQuery stateId
   (emojis, query) ->
@@ -288,7 +288,7 @@
   filteredEmojiListRawIndex stateId
   calculateListSelectedIndex
 ]
-filteredEmojiListSelectedItem  = (stateId) -> [
+filteredEmojiListSelectedItem = (stateId) -> [
   filteredEmojiList stateId
   filteredEmojiListSelectedIndex stateId
   getListSelectedItem
@@ -299,25 +299,25 @@
   CommonEmojiListSelectedIndexStore
   (indexes) -> indexes.get stateId
 ]
-commonEmojiListVisibility    = (stateId) -> [
+commonEmojiListVisibility = (stateId) -> [
   CommonEmojiListVisibilityStore
   (visibilities) -> visibilities.get stateId
 ]
 # Returns emoji from emoji list by current selected index
-commonEmojiListSelectedItem  = (stateId) -> [
+commonEmojiListSelectedItem = (stateId) -> [
   commonEmojiList
   commonEmojiListSelectedIndex stateId
   getListSelectedItem
 ]

-chatInputChannelsQuery         = (stateId) -> [
+chatInputChannelsQuery = (stateId) -> [
   ChatInputChannelsQueryStore
   (queries) -> queries.get stateId
 ]
 # Returns a list of channels depending on the current query
 # If query if empty, returns popular channels
 # Otherwise, returns channels filtered by query
-chatInputChannels              = (stateId) -> [
+chatInputChannels = (stateId) -> [
   ChannelsStore
   popularChannels
   chatInputChannelsQuery stateId
@@ -329,7 +329,7 @@
       channelName = channel.get('name').toLowerCase()
       return channelName.indexOf(query) is 0
 ]
-chatInputChannelsRawIndex      = (stateId) -> [
+chatInputChannelsRawIndex = (stateId) -> [
   ChatInputChannelsSelectedIndexStore
   (indexes) -> indexes.get stateId
 ]
@@ -348,14 +348,14 @@
   (visibilities) -> visibilities.get stateId
 ]

-chatInputUsersQuery         = (stateId) -> [
+chatInputUsersQuery = (stateId) -> [
   ChatInputUsersQueryStore
   (queries) -> queries.get stateId
 ]
 # Returns a list of users depending on the current query
 # If query is empty, returns selected channel participants
 # Otherwise, returns users filtered by query
-chatInputUsers              = (stateId) -> [
+chatInputUsers = (stateId) -> [
   UsersStore
   selectedChannelParticipants
   chatInputUsersQuery stateId
@@ -367,7 +367,7 @@
       userName = user.getIn(['profile', 'nickname']).toLowerCase()
       return userName.indexOf(query) is 0
 ]
-chatInputUsersRawIndex      = (stateId) -> [
+chatInputUsersRawIndex = (stateId) -> [
   ChatInputUsersSelectedIndexStore
   (indexes) -> indexes.get stateId
 ]
@@ -386,15 +386,15 @@
   (visibilities) -> visibilities.get stateId
 ]

-chatInputSearchItems         = (stateId) -> [
+chatInputSearchItems = (stateId) -> [
   ChatInputSearchStore
   (searchStore) -> searchStore.get stateId
 ]
-chatInputSearchQuery         = (stateId) -> [
+chatInputSearchQuery = (stateId) -> [
   ChatInputSearchQueryStore
   (queries) -> queries.get stateId
 ]
-chatInputSearchRawIndex      = (stateId) -> [
+chatInputSearchRawIndex = (stateId) -> [
   ChatInputSearchSelectedIndexStore
   (indexes) -> indexes.get stateId
 ]
@@ -403,12 +403,12 @@
   chatInputSearchRawIndex stateId
   calculateListSelectedIndex
 ]
-chatInputSearchSelectedItem  = (stateId) -> [
+chatInputSearchSelectedItem = (stateId) -> [
   chatInputSearchItems stateId
   chatInputSearchSelectedIndex stateId
   getListSelectedItem
 ]
-chatInputSearchVisibility    = (stateId) -> [
+chatInputSearchVisibility = (stateId) -> [
   ChatInputSearchVisibilityStore
   (visibilities) -> visibilities.get stateId
 ]


addded 8000 to the known http ports

addded 8000 to the known http ports


Fix bookmarklets. Closes #557.

Fix bookmarklets. Closes #557.

--- content_scripts/vomnibar.coffee
+++ content_scripts/vomnibar.coffee
@@ -200,11 +200,12 @@
     navigateToUrl: (url, openInNewTab) ->
       # If the URL is a bookmarklet prefixed with javascript:, we shouldn't open that in a new tab.
       if (url.indexOf("javascript:") == 0)
-        openInNewTab = false
-      chrome.extension.sendRequest(
-        handler: if openInNewTab then "openUrlInNewTab" else "openUrlInCurrentTab"
-        url: url,
-        selected: openInNewTab)
+        window.location = url
+      else
+        chrome.extension.sendRequest(
+          handler: if openInNewTab then "openUrlInNewTab" else "openUrlInCurrentTab"
+          url: url,
+          selected: openInNewTab)

     switchToTab: (tabId) -> chrome.extension.sendRequest({ handler: "selectSpecificTab", id: tabId })


Remove method prop from in-place-form

Remove method prop from in-place-form

--- app/components/in-place-form.cjsx
+++ app/components/in-place-form.cjsx
@@ -2,15 +2,12 @@

 module.exports = React.createClass
   displayName: 'InPlaceForm'
-
-  getDefaultProps: ->
-    method: 'post'

   overrideSubmit: (e) ->
     e.preventDefault()
     @props.onSubmit? e

   render: ->
-    <form method={@props.method} className="in-place-form" onSubmit={@overrideSubmit}>
+    <form className="in-place-form" onSubmit={@overrideSubmit}>
       {@props.children}
     </form>


Sim freq levels set to 30 long

Sim freq levels set to 30 long

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -574,7 +574,7 @@

     # When duds are included in the @TrainingGroup, need to multiply @simratio by 2 to
     nClassified = @counter.classified
-    baseLevel = Math.floor(nClassified / 20) + 1
+    baseLevel = Math.floor(nClassified / 30) + 1
     @level = Math.min(baseLevel, 3)
     denominator = (5 * Math.pow(Math.sqrt(2), @level - 1))
     @simRatio = 2 / denominator


disable marking when showing known transits

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -604,7 +604,7 @@
     @sim_count ||= 0
     @sim_count +=1

-    @sim_rate = 5
+    @sim_rate = 2

     if @sim_count % @sim_rate == 0
       Subject.group = SIMULATION_GROUP
@@ -747,6 +747,8 @@

   displayKnownTransits: ->
     return unless @simulationsPresent()
+    console.log 'MARKING DISABLED'
+    @canvasGraph.disableMarking()
     console.log "on display", @known_transitss
     @hideMarkingButtons()
     @continueButton.show()


Don't explode when require.resolve doesn't find a file. Return null instead.

--- src/stdlib/require.coffee
+++ src/stdlib/require.coffee
@@ -19,10 +19,12 @@
   code = __read file
   window.eval(code + "\n//@ sourceURL=" + file)

-require = (file, cb) ->
-  return cb require file if cb?
+require = (path, cb) ->
+  return cb require path if cb?

-  file  = resolve file
+  unless file = resolve(path)
+    throw new Error("Require can't find file at path '#{path}'")
+
   parts = file.split '.'
   ext   = parts[parts.length-1]

@@ -88,10 +90,10 @@
   else
     file = __expand(file) or file

-  if file[0] isnt '/'
-    throw "require: Can't find '#{file}'"
-
-  return file
+  if file[0] == '/'
+    file
+  else
+    null

 __expand = (path) ->
   return path if __isFile path


Show uncaught exception in message dialog.

--- browser/atom/atom.coffee
+++ browser/atom/atom.coffee
@@ -26,10 +26,14 @@

 # Don't quit on fatal error.
 process.on 'uncaughtException', (error) ->
-  # TODO Show error in GUI.
+  # Show error in GUI.
   message = error.stack ? "#{error.name}: #{error.message}"
-  console.error 'uncaughtException:'
-  console.error message
+  require('dialog').showMessageBox
+      type: 'warning'
+      title: 'An javascript error occured in the browser'
+      message: 'uncaughtException'
+      detail: message
+      buttons: ['OK']

 # Load the RPC server.
 require './rpc-server.js'


Extracts local player binary, some formattings.

--- bin/index.coffee
+++ bin/index.coffee
@@ -2,6 +2,8 @@

 {App} = require '../lib/app'
 {EventEmitter2} = require 'eventemitter2'
+
+MY_LOCAL_PLAYER = '/usr/bin/afplay'

 app = new App(new EventEmitter2 wildcard: true)

@@ -33,16 +35,27 @@
           'event': 'plugin.jenkins.job.init'
           'plugins': ['say']
           'fn': (data) ->
-            @say.convertTextToSpeech text: "Project #{data.response.name} has state #{data.state ? 'unknown'}.", strategy: 'google', player: 'cli', playerArg: '/usr/bin/afplay'
+            @say.convertTextToSpeech
+              text: "Project #{data.response.name} has state #{data.state ? 'unknown'}."
+              strategy: 'google'
+              player: 'cli'
+              playerArg: MY_LOCAL_PLAYER
         'two':
           'event': 'plugin.jenkins.job.state'
           'plugins': ['say']
           'fn': (data) ->
-            @say.convertTextToSpeech text: "Project #{data.response.name} has changed to #{data.state ? 'unknown'}.", strategy: 'google', player: 'cli', playerArg: '/usr/bin/afplay'
+            @say.convertTextToSpeech
+              text: "Project #{data.response.name} has changed to #{data.state ? 'unknown'}."
+              strategy: 'google'
+              player: 'cli'
+              playerArg: MY_LOCAL_PLAYER
         'three':
           'event': 'plugin.jenkins.job.refresh'
           'plugins': ['say']
           'fn': (data) ->
-            @say.convertTextToSpeech text: "Project #{data.response.name} was refreshed.", strategy: 'google', player: 'cli', playerArg: '/usr/bin/afplay'
+            @say.convertTextToSpeech text: "Project #{data.response.name} was refreshed."
+              strategy: 'google'
+              player: 'cli'
+              playerArg: MY_LOCAL_PLAYER

 app.start()


here's what an exmaple might look like...

--- examples/encrypt.iced
+++ examples/encrypt.iced
@@ -1,27 +1,78 @@

 libkb = require 'libkeybase'
 {User} = libkb
+
+# Your app needs to provide some idea of local storage that meets our requirements.
 {LocalStore} = require 'myapp'

-
-# First load me from the server, and check that my signature chain checks out.
-# Also, assert that my PGP key matches the give username.  This feature is optional.
-# You can alternatively provide a key manager here, for the key assertion.
-await User.fetch_user { query : { keybase : "max" }, assertions : [ { key : "aabbccdd" } ] }, defer err, me
-
-# You can alternatively assert in a separate call
-await user.assert [ { key : "aabbccdd" } ], defer err
-
+# Open the LocalStore, which can create one if none existed beforehand.
 await LocalStore.open defer err, store

-mode = # Strict, or flexible.  If flexible, then we can return a "dummy" user
-       # that implements the methods below.  If strict and not found, then we'll
-       # raise an Error.
+# Steps to loading a user:
+#
+#   1. Fetching all signature data from the server (or from local storage)
+#   2. Cryptographic Checks
+#       a. Checking the tail of the sigchain is in the Merkle Tree.
+#       b. Checking the merkle tree is in the blockchain (optional)
+#       c. Checking all links of the signature chain point to each other.
+#       d. Checking that the tail is signed by the most recent active public key (the user might have
+#          switched halfway down the chain).
+#   3. Identity Table Construction - Flatten the signature chain into a final "identity table"
+#      of remote identities that link to this username/key combination.
+#
+# Next, 4a, b, and c can happen in any order:
+#
+#   4a. Remote check --- check that all proofs in the identity table still exist
+#   4b. Tracking resolution --- check the computed identity table against any existing tracker statements,
+#       and resolve what needs to be fixed to bring any stale tracking up-to-date
+#   4c. Assertions -- check the user's given assertions against the computed identity table
+#
+# Next, 5 can happen only after all of 4a, b, and c
+#
+#   5. track/retrack -- sign a new tracking statement, if necessary, signing off on the above computations.
+#

-await User.fetch_user { query : { "twitter" : "malgorithms" }, mode }, defer err, user
+# Load a user from the server, and perform steps 1, 2, and 3.  Recall that step 2b is optional,
+# and you can provide options here to enable it.  If you do provide that option, there might be a
+# latency of up to 6 hours.
+#
+# The Store is optional, but if provided, we can check the store rather than
+# fetch from the server.
+await User.load { store, query : { keybase : "max" }, opts : {} }, defer err, me

-# Check tracking for this use.  Check the local store, and/or check my tracking statements
-# remotely fetched for 'me'.
-await user.check_tracking { store, tracker : me }, defer err, summary
+# As in 4c above...
+await me.assert { assertions : [ { key : "aabbccdd" }, { "reddit" : "maxtaco" }, { "web" : "https://goobar.com" } ] }, defer err

-await user.fetch_key_manager { { app : "myencryptor" }, mode }, defer err, km
+# Load a second user...
+await User.load { store, query : { "twitter" : "malgorithms" } }, defer err, chris
+
+# As in 4b above...
+#
+# State can be: NONE, if I haven't tracked Chris before; OK if my tracking
+# statement is fully up-to-date, STALE if my tracking statement is out-of-date,
+# or SUBSET, if it's a proper subset of the current state.
+#
+await chris.check_tracking { tracker : me }, defer err, state
+
+# As in 4a above.
+#
+# An error will be returned if there was a catastrophic failure, not if
+# any one of the proofs failed. Check the status field for OK if all succeded, or
+# PARTIAL_FAILURE if some failed.
+#
+# Note that there is a 1-to-1 correspondence between the IdentityTable object and the
+# User object, but they are split apart for convenience.
+#
+idtab = chris.get_identity_table()
+await idtab.check_remotes {}, defer err, status
+
+# As in 4c, optional assertions against the identity table
+await idtab.assert { assertions : [ { "key" : "aabb" }, { "reddit" : "maxtaco" } ] }, defer err
+
+# Outputs any failures in JSON format, though you can query the idtab in a number of different ways
+# (which aren't finalized yet...)
+failures = idtab.get_failures_to_json()
+
+# Fetch a key manager for a particular app (or for the main app if none specified), and for
+# the given public key operations.
+await chris.fetch_key_manager { { app : "myencryptor" }, ops }, defer err, km


Revert "debugging: adds log messages"

Revert "debugging: adds log messages"

This reverts commit be856a3addefb2e97968f3ec3b199bfc5f8ef55a.

--- public/app.coffee
+++ public/app.coffee
@@ -6,10 +6,8 @@
     "https://site-prototype.herokuapp.com"

   @apiUrl: if window.location.hostname.match(/^localhost|127\.|0\./)
-    console.log "I AM LOCALHOST", window.location
     "http://localhost:4322/rs"
   else
-    console.log "I AM SOMETHING ELSE", window.location
     "https://pm.epages.com/rs"


update regex

update regex

--- parse.coffee
+++ parse.coffee
@@ -127,9 +127,9 @@
 parseFile = (s) ->
   return if not s

-  fileNames = s.match(/a\/.*(?= b)|b\/.*$/g)
+  fileNames = s.match(/a\/.*(?=["']? ["']?b\/)|b\/.*$/g)
   fileNames.map (fileName, i) ->
-    fileNames[i] = fileName.replace(/^(a|b)\//, '')
+    fileNames[i] = fileName.replace(/^(a|b)\//, '').replace(/("|')$/, '')

   return fileNames


Minor fix in uploading URL

Minor fix in uploading URL

--- app/uploaders/gcs-uploader.coffee
+++ app/uploaders/gcs-uploader.coffee
@@ -47,7 +47,7 @@
         else
           Notify.error "Error While signing the file."

-      url  = json.base_url
+      url  = json.url
       headers = json.headers
       settings =
         url: url


Add 'Install Shell Commands' menu item.

Add 'Install Shell Commands' menu item.

--- menus/darwin.cson
+++ menus/darwin.cson
@@ -10,6 +10,8 @@
       { label: 'Open Your Config', command: 'application:open-your-config' }
       { label: 'Open Your Keymap', command: 'application:open-your-keymap' }
       { label: 'Open Your Stylesheet', command: 'application:open-your-stylesheet' }
+      { type: 'separator' }
+      { label: 'Install Shell Commands', command: 'window:install-shell-commands' }
       { type: 'separator' }
       { label: 'Hide Atom', command: 'application:hide' }
       { label: 'Hide Others', command: 'application:hide-other-applications' }


:apple: Add "Services" menu, fixes #3204

--- menus/darwin.cson
+++ menus/darwin.cson
@@ -17,6 +17,8 @@
       { label: 'Open Your Stylesheet', command: 'application:open-your-stylesheet' }
       { type: 'separator' }
       { label: 'Install Shell Commands', command: 'window:install-shell-commands' }
+      { type: 'separator' }
+      { label: 'Services', submenu: [] }
       { type: 'separator' }
       { label: 'Hide Atom', command: 'application:hide' }
       { label: 'Hide Others', command: 'application:hide-other-applications' }


Include ImageSizes mixin

Include ImageSizes mixin

--- models/partner_show.coffee
+++ models/partner_show.coffee
@@ -13,6 +13,7 @@
 { compactObject } = require './mixins/compact_object.coffee'
 fetchUntilEnd = require('artsy-backbone-mixins').Fetch(sd.API_URL).fetchUntilEnd
 moment = require 'moment'
+ImageSizes = require './mixins/image_sizes.coffee'

 module.exports = class PartnerShow extends Backbone.Model

@@ -21,6 +22,7 @@
   maxFeedArtworks: 8

   _.extend @prototype, Image(sd.SECURE_IMAGES_URL)
+  _.extend @prototype, ImageSizes

   url: ->
     if @has('partner')


Fix selective purge

Fix selective purge

--- modules/admin/index.coffee
+++ modules/admin/index.coffee
@@ -42,7 +42,7 @@
         try
           messages = await msg.channel.fetchMessages { limit }
           if msg.mentions.members.first()?
-            messages = messages.filter (m)-> m.member.id is msg.mentions.members.first()
+            messages = messages.filter (m)-> m.member.id is msg.mentions.members.first().id
           await msg.channel.bulkDelete(messages)
         catch e
           Core.log(e, 1)


added todo comment so I can find it again later :D

--- sources/tradeassist.base.coffee
+++ sources/tradeassist.base.coffee
@@ -42,7 +42,8 @@
   # bind control icons and popup events
   constructor: ->
     @region = "eu"
-#    @region = "us" if (navigator.language or navigator.userLanguage or "").match(RegExp('^en-us','i'))?
+    # todo: uncomment to enable region preselection
+    @region = "us" if (navigator.language or navigator.userLanguage or "").match(RegExp('^en-us','i'))?
     $('body').addClass 'region-'+@region
     @loadLists window.location.hash.substr(1) if window.location.hash isnt ""
     # bind hooks


refactor git-add-spec.coffee

refactor git-add-spec.coffee

--- spec/models/git-add-spec.coffee
+++ spec/models/git-add-spec.coffee
@@ -1,26 +1,16 @@
-Path = require 'flavored-path'
-
+{repo, pathToRepoFile} = require '../fixtures'
 git = require '../../lib/git'
 GitAdd = require '../../lib/models/git-add'
-
-pathToRepoFile = Path.get "~/some/repository/directory/file"
-mockRepo =
-  getWorkingDirectory: -> Path.get "~/some/repository"
-  refreshStatus: -> undefined
-  relativize: (path) -> "directory/file" if path is pathToRepoFile
-  repo:
-    submoduleForPath: (path) -> undefined

 describe "GitAdd", ->
   it "calls git.add with the current file if `addAll` is false", ->
     spyOn(git, 'add')
     spyOn(atom.workspace, 'getActiveTextEditor').andCallFake ->
       getPath: -> pathToRepoFile
-
-    GitAdd(mockRepo)
-    expect(git.add).toHaveBeenCalledWith mockRepo, file: mockRepo.relativize(pathToRepoFile)
+    GitAdd(repo)
+    expect(git.add).toHaveBeenCalledWith repo, file: repo.relativize(pathToRepoFile)

   it "calls git.add without a file option if `addAll` is true", ->
     spyOn(git, 'add')
-    GitAdd(mockRepo, addAll: true)
-    expect(git.add).toHaveBeenCalledWith mockRepo
+    GitAdd(repo, addAll: true)
+    expect(git.add).toHaveBeenCalledWith repo


:white_check_mark: now async editor.save aware

--- spec/project-folder-spec.coffee
+++ spec/project-folder-spec.coffee
@@ -287,51 +287,55 @@
         expect(userConfigEditor.getPath()).toBe(configPath)

       it "load config on save", ->
-        dispatchCommand(workspaceElement, 'project-folder:add')
-        expect(view).toHaveClass('add')
-
-        ensureSelectListItems [
-          itemDirNormalDir1
-          itemDirNormalDir2
-        ]
-
-        view.cancel()
-        expect(view.getItemsForGroups()).toEqual([])
-        userConfigEditor.setText """
-          groups:
-            atom: [
-              "~/github/atom.org"
-              "~/github/text-buffer"
-              "~/github/atom-keymap"
-            ]
-            hello: [
-              "~/dir/hello-project"
-              "~/dir/world-project"
-            ]
-          """
-        userConfigEditor.save()
-
-        itemGroupAtom =
-          name: 'atom'
-          dirs: ["~/github/atom.org", "~/github/text-buffer", "~/github/atom-keymap"].map(normalize)
-
-        itemGroupHello =
-          name: 'hello'
-          dirs: ["~/dir/hello-project", "~/dir/world-project"].map(normalize)
-
-        expect(view.getItemsForGroups()).toEqual [
-          itemGroupAtom
-          itemGroupHello
-        ]
-
-        dispatchCommand(workspaceElement, 'project-folder:add')
-        expect(view).toHaveClass('add')
-        ensureSelectListItems [
-          itemGroupAtom
-          itemGroupHello
-          itemDirNormalDir1
-          itemDirNormalDir2
-        ]
+        runs ->
+          dispatchCommand(workspaceElement, 'project-folder:add')
+          expect(view).toHaveClass('add')
+
+          ensureSelectListItems [
+            itemDirNormalDir1
+            itemDirNormalDir2
+          ]
+
+          view.cancel()
+          expect(view.getItemsForGroups()).toEqual([])
+          userConfigEditor.setText """
+            groups:
+              atom: [
+                "~/github/atom.org"
+                "~/github/text-buffer"
+                "~/github/atom-keymap"
+              ]
+              hello: [
+                "~/dir/hello-project"
+                "~/dir/world-project"
+              ]
+            """
+
+        waitsForPromise ->
+          Promise.resolve(userConfigEditor.save())
+
+        runs ->
+          itemGroupAtom =
+            name: 'atom'
+            dirs: ["~/github/atom.org", "~/github/text-buffer", "~/github/atom-keymap"].map(normalize)
+
+          itemGroupHello =
+            name: 'hello'
+            dirs: ["~/dir/hello-project", "~/dir/world-project"].map(normalize)
+
+          expect(view.getItemsForGroups()).toEqual [
+            itemGroupAtom
+            itemGroupHello
+          ]
+
+          dispatchCommand(workspaceElement, 'project-folder:add')
+          expect(view).toHaveClass('add')
+          ensureSelectListItems [
+            itemGroupAtom
+            itemGroupHello
+            itemDirNormalDir1
+            itemDirNormalDir2
+          ]

     describe "add/remove groups of project", ->
       loadUserConfig = ->
@@ -347,7 +351,7 @@
             ]
           """
         userConfigEditor.save()
-        expect(view.groups).not.toBe(null)
+        Promise.resolve(userConfigEditor.save())

       beforeEach ->
         setConfig('gitProjectDirectories', [gitRoot])
@@ -355,7 +359,10 @@
         # By changing showGroupOnRemoveListCondition from default 'never' to
         # 'some-member-was-loaded', we can test removal of group.
         setConfig('showGroupOnRemoveListCondition', 'some-member-was-loaded')
-        loadUserConfig()
+
+        waitsForPromise ->
+          loadUserConfig().then ->
+            expect(view.getItemsForGroups()).toHaveLength(2)

       it "add/remove set of project defined in groups", ->
         dispatchCommand(workspaceElement, 'project-folder:add')


Redirect after creating a registration link

--- routes/api.coffee
+++ routes/api.coffee
@@ -152,7 +152,7 @@
   code = randomString(30)
   users.createUser { displayName: req.body.displayName, registerID: code },
     user ->
-      res.respond users.getUsers()
+      res.redirect '/admin/admin'

 router.get '/getUsers', (req, res) ->


Slightly cleaner

Slightly cleaner

--- src/component/AttributeTemplateParser.coffee
+++ src/component/AttributeTemplateParser.coffee
@@ -30,6 +30,7 @@
         out

     zeroPad = (num) -> if num < 10 then "0#{num}" else "#{num}"
+    noopZeroPad = (num) -> "#{num}"

     constructor: (@displayAttribute = DOM_DISPLAY_ATTRIBUTE, @hidableAttribute = DOM_HIDABLE_ATTRIBUTE) ->

@@ -41,6 +42,8 @@
         hidableAttributeKey = "data-#{@hidableAttribute}" #Cache key because function scope messes with it
         filteredHidable = Array::filter.call hidableElements, (x) ->
             x.getAttribute(hidableAttributeKey) is key
+
+        localZeroPad = if shouldZeroPad then zeroPad else noopZeroPad

         previousValue = Number.NaN

@@ -50,7 +53,7 @@
                 [value, significant] = period.getUnit key
                 if previousValue isnt value
                     previousValue = value
-                    paddedValue = if shouldZeroPad then zeroPad(value)  else "#{value}"
+                    paddedValue = localZeroPad value

                     displayElem.innerHTML = paddedValue for displayElem in filteredDisplay


Added setemptystring snippet

Added setemptystring snippet

--- completions.cson
+++ completions.cson
@@ -1701,6 +1701,12 @@
   )${0}
   """

+'setEmptyString':
+  'prefix': 'setemptystring'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': 'setEmptyString(\'${1}\')${0}'
+
 'LookupField':
   'prefix': 'lookupfield'
   'module': 'silverstripe/framework'


fix scope binding error

fix scope binding error

--- library/exposure/access.coffee
+++ library/exposure/access.coffee
@@ -65,7 +65,7 @@
         return callback() unless content = session[sid]
         delete container[key] if _.has container, key
         @ressurectEntity ?= (xc, xn) -> xn null, xc
-        @ressurectEntity content, (error, entity) ->
+        @ressurectEntity content, (error, entity) =>
             format = (m) -> "ressurection error: #{m}"
             masked = format error.message if error
             return callback Error masked if error
@@ -113,7 +113,7 @@
         format = (m) -> "ingition access error: #{m}"
         logger.debug "Ingition dereferencing at #{id}"
         success = "Got valid ignition entity at #{id}"
-        try @dereference request, (error, supply) ->
+        try @dereference request, (error, supply) =>
             @emit "entity-ignition", arguments...
             succeeded = _.isObject request.entity
             logger.debug success.green if succeeded
@@ -132,7 +132,7 @@
         format = (m) -> "handshake access error: #{m}"
         logger.debug "Handshake dereferencing at #{id}"
         success = "Got valid handshake entity at #{id}"
-        try @dereference handshake, (error, supply) ->
+        try @dereference handshake, (error, supply) =>
             @emit "entity-handshake", arguments...
             succeeded = _.isObject handshake.entity
             logger.debug success.green if succeeded


emit a signal when hit the limits

emit a signal when hit the limits

--- library/exposure/memory.coffee
+++ library/exposure/memory.coffee
@@ -66,6 +66,7 @@
             detected = "#{humanized.heapTotal}".bold
             return undefined unless overflow is yes
             logger.warn hits.toString().red, detected
+            kernel.emit "mem-limits", memory, limit
             kernel.shutdownKernel undefined, no

     # A hook that will be called each time when the kernel beacon


Better var names

Better var names

--- library/membrane/duplex.coffee
+++ library/membrane/duplex.coffee
@@ -174,7 +174,7 @@
     # instance at the server site. This implementation that uses the
     # `upstreamAsync` mechanism to invoke the `connected` method at all
     # peers of the inheritance hierarchy. Refer to the method for info.
-    channelOpened: @provider (context, callback) ->
+    trampoline: @provider (context, callback) ->
         identify = @constructor.identify?()
         isocket = "Notified from socket %s"
         message = "Inbound duplex connection at %s"
@@ -190,7 +190,7 @@
     # client end of the Socket.IO channel and creates wrapper around
     # all the providers residing in the current service implementation.
     # Refer to other `Duplex` methods for understanding what goes on.
-    openChannel: @autocall ->
+    bootloader: @autocall ->
         foreign = (value) -> _.isObject value.socket
         try @socket = io.connect @duplex catch error
             message = "blew up Socket.IO: #{error.message}"
@@ -209,7 +209,7 @@
             assert @consumeProviders; @consumeProviders @socket
             open = "notified the service of an opened channel"
             confirm = => console.log open; @emit "booted"
-            @channelOpened _.omit(@, foreign), confirm
+            @trampoline _.omit(@, foreign), confirm

     # An external routine that will be invoked once a both way duplex
     # channel is established at the client site. This will normally


start cutting on the extending

start cutting on the extending

--- library/nucleus/extends.coffee
+++ library/nucleus/extends.coffee
@@ -52,6 +52,7 @@
     # subclass of the supplied archetype class (as argument). Of course
     # it is assumed that you should be invoking this method only on the
     # objects that are valid CoffeeSscript classes with necessary attrs.
+    # Otherwise, an exception will be triggered to indicare usage error.
     Object.defineProperty Object::, "derives",
         enumerable: no, value: (archetype, loose) ->
             notObject = "supplied acrhetype is not object"


Fix the typo "exmaple"

Fix the typo "exmaple"

--- library/vending/mirrors.coffee
+++ library/vending/mirrors.coffee
@@ -64,7 +64,7 @@
             pathname: _.head(constructor.resources)?.unescape()
             patterns: _.map constructor.resources, "source"
             methods: _.map record.methods, (doc, method) ->
-                exmaple: doc.example()
+                example: doc.example()
                 argument: doc.argument()
                 synopsis: doc.synopsis()
                 results: doc.results()


Update evalutation method in svg-graph-view to add maxIn.

--- src/code/views/svg-graph-view.coffee
+++ src/code/views/svg-graph-view.coffee
@@ -47,7 +47,7 @@
     miny = Infinity
     maxy = -Infinity
     data = _.map data, (x) =>
-      scope = {in: x, out: 0}
+      scope = {in: x, out: 0, maxIn: rangex}
       try
         y = math.eval @props.formula, scope
         if y < miny then miny = y


Revert "add support for new homepage rails and update rails order"

--- src/desktop/apps/home/queries/initial.coffee
+++ src/desktop/apps/home/queries/initial.coffee
@@ -6,14 +6,12 @@
         max_followed_gene_rails: -1,
         order: [
           ACTIVE_BIDS,
-          RECENTLY_VIEWED_WORKS,
-          SIMILAR_TO_RECENTLY_VIEWED,
+          RECENTLY_VIEWED_WORKS
+          RECOMMENDED_WORKS,
+          FOLLOWED_ARTISTS,
+          RELATED_ARTISTS,
+          FOLLOWED_GALLERIES,
           SAVED_WORKS,
-          SIMILAR_TO_SAVED_WORKS,
-          FOLLOWED_ARTISTS,
-          FOLLOWED_GALLERIES,
-          RECOMMENDED_WORKS,
-          RELATED_ARTISTS,
           LIVE_AUCTIONS,
           CURRENT_FAIRS,
           FOLLOWED_GENES,


Fix failing test

Fix failing test

- Where did underscore.string go?

--- server/rss.coffee
+++ server/rss.coffee
@@ -2,7 +2,8 @@

   serveRSS: () ->
     RSS = Npm.require('rss')
-    host = _.trim Meteor.absoluteUrl(), '/'
+    host = Meteor.absoluteUrl()
+    host = host.slice(0, host.length - 1)

     feed = new RSS
       title: Blog.settings.rss.title


Remove spurious control characters from RSS

--- smf.coffee
+++ smf.coffee
@@ -146,6 +146,8 @@
       log.error "unable to fetch page #{item.link}: #{e.message}"

 processRSS = (rss) ->
+  # sanitize string, removing spurious control characters
+  rss = rss.replace(/\x1f/g, '')
   try
     j = parser.toJson rss, object: true
     items = j.rss?.channel?.item


added `uninstall` command to component

added `uninstall` command to component

--- component.coffee
+++ component.coffee
@@ -13,6 +13,7 @@
   fs.writeFileSync(fname, content, encoding)
 exists = path.existsSync
 join = path.join
+basename = path.basename
 pathSep = path.normalize("/")
 argv = null
 usage = null
@@ -57,11 +58,13 @@
   name ||= componentName()
   if not exists join(tgtDir, name)
     if link
+      log "link-installing #{name} to #{tgtDir}"
       orig = path.resolve process.cwd()
       process.chdir tgtDir
       fs.symlinkSync join(orig, src), name, "dir"
       process.chdir orig
     else
+      log "installing #{name} to #{tgtDir}"
       wrench.copyDirSyncRecursive src, join(tgtDir, name)

 allNodeModules = () ->
@@ -89,6 +92,7 @@

 provide = (pth) ->
   if not exists pth
+    log "recursively creating directory `#{pth}`"
     start = if pth[0] is pathSep then pathSep else ""
     _.reduce [start].concat(pth.split(pathSep)), (soFar, dir) ->
       fs.mkdirSync join(soFar, dir) if not exists(join(soFar, dir))
@@ -118,7 +122,6 @@
     if not isComponent() then return {}
     destPath = join(root, componentName())
     addComponentToConfig(makePathsAbsolute(requirejsConfig(), destPath))
-  console.log 'in there',argv.baseurlSpecified,argv.baseurl
   baseUrl = if argv.baseurlSpecified then {baseUrl: argv.baseurl} else {}
   extend.apply this, [true, baseUrl]
     .concat(componentConfig(c) for c in components)
@@ -135,6 +138,8 @@
   require(join(component.path, component.json.component.testMiddleware)) \
     for component in components when component.json.component.testMiddleware

+componentsPath = () ->
+  join argv.staticpath, "components"

 stringBundles = (dir = ".") ->
   stringsYml = join(dir, "strings.yml")
@@ -173,6 +178,12 @@
     stringBundlesAsRequirejsModule()
   config: (config) ->
     JSON.stringify config, null, "    "
+  all: (config) ->
+    _.reduce(templateCommands, ((templateObj, cmd, name) ->
+      return templateObj if name is "all"
+      templateObj[name] = templateCommands[name](config)
+      return templateObj
+    ), {})

 exports.commands = commands =
   install:
@@ -182,14 +193,13 @@
     action: () ->
       provide argv.staticpath
       components = allComponents()
-      componentsDirName = join argv.staticpath, "components"
-      provide componentsDirName
+      provide componentsPath()
       for component in components
         src = join(component.path, sourceDirectory(component.path))
         name = componentName(null, component.json)
-        installTo componentsDirName, argv.link, src, name
+        installTo componentsPath(), argv.link, src, name
       if isComponent()
-        installTo componentsDirName, argv.link, 'src', componentName()
+        installTo componentsPath(), argv.link, 'src', componentName()

   test:
     description: """
@@ -208,8 +218,8 @@
           extraHtml,
           getTestMiddleware())
         .listen(argv.port, argv.host)
-      console.log "serving at http://#{argv.host}:#{argv.port}"
-      console.log "available tests:"
+      log "serving at http://#{argv.host}:#{argv.port}"
+      log "available tests:"
       listTests tests, argv.host, argv.port

   template:
@@ -218,37 +228,55 @@
     #{(' - ' + cmd + '\n' for cmd of templateCommands).join("")}
     """.replace(/\n$/, '')
     action: (cmd, args...) ->
-      templateCommands[cmd] getConfig()
+      console.log templateCommands[cmd](getConfig())

   build:
     description: """
     the build command does two things:
-     - run `component install` IF the [staticpath] is given
-     - and if the [templatepath] is given, it will output a json file named
+     - run `component install`
+     - if the [templatepath] is given, output a json file named
        [contextjsonname] to that path containing the keyed info from the
        `component template` command
     """
     action: () ->
-      commands.install.action() if argv.staticpathSpec
+      commands.install.action()

       if argv.templatepath
-        config = getConfig()
-        templateObj = _.reduce(templateCommands, ((templateObj, cmd, name) ->
-          templateObj[name] = templateCommands[name](config)
-          return templateObj
-        ), {})
+        templateObj = templateCommands.all getConfig()
         provide argv.templatepath
         contextjsonname = join(argv.templatepath, argv.contextjsonname)
+        log "writing context json to #{contextjsonname}"
         write contextjsonname, JSON.stringify(templateObj)

   completion:
     description: """
     spits out a bash completion command.  something you can run like this:
-      $ `component completion`
-    """
-    action: () ->
-      console.log "complete -W #{(c for c of commands).join(" ")} component"
-
+      $ component completion > /tmp/cc.bash && source /tmp/cc.bash
+    """
+    action: () ->
+      console.log "complete -W \"#{(c for c of commands).join(" ")}\" component"
+
+  uninstall:
+    description: """
+    just does the opposite of the `component install` command -- it removes
+    directories (or links) from [staticpath] that would have been installed
+    """
+    action: () ->
+      components = allComponents()
+      if isComponent()
+        components.push {json: {component: {name: componentName()}}}
+      for component in components
+        installedTo = join(componentsPath(), component.json.component.name)
+        if exists installedTo
+          if fs.lstatSync(installedTo).isSymbolicLink()
+            log "removing link #{installedTo}"
+            fs.unlinkSync installedTo
+          else
+            log "removing directory #{installedTo}"
+            wrench.rmdirSyncRecursive installedTo
+
+log = (msg, level="info") ->
+  console.log "[#{basename process.argv[1]} #{argv._[0]}] #{msg}"

 usage = """#{("node $0 "+cmd+'\n' for cmd of commands).join("")}
 `component` is a utility that's used for installing javascript components and
@@ -307,7 +335,13 @@
       "default": join json.component?.testDirectory or
                       (exists("static") and ".") or
                       ".test", "static"
-      describe: "specify the installation path (dynamic)\n(cmd: build)"
+      describe: """
+      specify the installation path.  the default for this value is dynamically determined:
+       - if there is a package.json file with a component.testDirectory property, staticpath is set to that
+       - otherwise if the './static' directory exits, staticpath is set to 'static'
+       - finally if nothing else component will create a '.test' directory and use it as the staticpath
+       (cmd: build)
+       """

     .option "baseurl",
       string: true


Change version

Change version

--- source/atoms.coffee
+++ source/atoms.coffee
@@ -1,6 +1,6 @@
 # Globals
 Atoms = @Atoms =
-  version   : "0.03.19"
+  version   : "0.03.22"
   Core      : {}
   Class     : {}


more subtree specs

more subtree specs

--- spec/Subtree.coffee
+++ spec/Subtree.coffee
@@ -37,6 +37,22 @@
             chai.expect(d).to.eql expected
             done()

+      describe "then -> child.then", ->
+        it "it should return #{expected}", (done) ->
+          t = Root()
+          t.deliver {}
+          .then (n,d) ->
+            child n,d
+            .then (n,d) ->
+              child: d
+              state: 'w-child'
+          .else (n,d) ->
+            child: d
+            state: 'wo-child'
+          .finally (c, d) ->
+            chai.expect(d).to.eql expected
+            done()
+
     describe 'succeed via return', ->
       child = (parent, data) ->
         parent.tree 'child'


[client][userlist] initialize at the right time

Fixes #137

--- src/frontend/modules/userList.coffee
+++ src/frontend/modules/userList.coffee
@@ -22,13 +22,16 @@
         @jqContainer = ($ 'ul#feeds')
         @columns     = 1
         @users       = { }
-        @users[window.Voicious.currentUser.uid] = window.Voicious.currentUser
-        @users[window.Voicious.currentUser.uid]['isLocal'] = on
-        @users[window.Voicious.currentUser.uid]['volume'] = on
         do @configureEvents
         do @display
         ($ window).on 'resize', () =>
             do @updateColumns
+
+    initialize          : () =>
+        @users[window.Voicious.currentUser.uid] = window.Voicious.currentUser
+        @users[window.Voicious.currentUser.uid]['isLocal'] = on
+        @users[window.Voicious.currentUser.uid]['volume'] = on
+        do @display

     configureEvents     : () =>
         @emitter.on 'peer.list', @fill


Adds spec on the scope of the aspects

Adds spec on the scope of the aspects

--- spec/index.coffee
+++ spec/index.coffee
@@ -92,7 +92,18 @@
         @original.should.have.been.calledBefore spy1
         spy1.should.have.been.calledBefore spy2

-  xdescribe 'scope of aspects', ->
+  describe 'scope of aspects', ->
+    it 'should not be the scope of the original function', ->
+      scope = {}
+      scopedFn = (->).bind(scope)
+      fnWithAspects = maypop(scopedFn)
+      beforeFn = sinon.spy ->
+      afterFn = sinon.spy ->
+      fnWithAspects.before beforeFn
+      fnWithAspects.after afterFn
+      fnWithAspects()
+      beforeFn.should.not.have.been.calledOn scope
+      afterFn.should.not.have.been.calledOn scope
   xdescribe "Adding multiple aspects at once", ->
   xdescribe "exceptions", ->


namespaced unbind

namespaced unbind

--- src/bindable.coffee
+++ src/bindable.coffee
@@ -73,7 +73,7 @@
   unbind: (events, callback) ->
     events.split(" ").each (namespacedEvent) ->
       [event, namespaces...] = namespacedEvent.split(".")
-
+
       eventCallbacks[event] = eventCallbacks[event] || []

       if callback


Fixes bug in value binding which turns null/undefined to string

--- src/bindings.coffee
+++ src/bindings.coffee
@@ -308,7 +308,9 @@
         element.find('input:radio[value="' + value + '"]').prop('checked', true)
     else
       (value) ->
-        element.val(selectValueField and value[selectValueField] or value)
+        strValue = selectValueField and value[selectValueField] or value
+        strValue = '' + strValue if strValue?
+        element.val(strValue)
         element.data('value', value) if selectValueField

   observer = controller.watch watchExpr, (value) ->
@@ -320,7 +322,7 @@

   # Sets initial element value. For SELECT elements allows child option element values to be set first.
   if element.is('select')
-    setTimeout ->
+    controller.afterSync ->
       setValue controller.eval expr
       unless element.is('[readonly]')
         controller.evalSetter expr, getValue(true)


FRAMEWORK: kdtabpaneview now slugifies/lowercases the name option to a css class

--- client/Framework/components/tabs/KDTabPaneView.coffee
+++ client/Framework/components/tabs/KDTabPaneView.coffee
@@ -1,12 +1,12 @@
 class KDTabPaneView extends KDView
   constructor:(options = {},data)->
-
+
     options.hiddenHandle ?= no      # yes or no
     options.name        or= ""      # a String
-    options.cssClass      = KD.utils.curryCssClass "kdtabpaneview kdhiddentab clearfix #{options.name}"
+    options.cssClass      = KD.utils.curryCssClass "kdtabpaneview kdhiddentab clearfix #{KD.utils.slugify(options.name.toLowerCase())}"

     super options, data
-
+
     @name = options.name
     @setClass "clearfix"
     @setHeight @$().parent().height()


activity: fix route param

activity: fix route param

--- client/activity/lib/routes/singlepublicchannel.coffee
+++ client/activity/lib/routes/singlepublicchannel.coffee
@@ -18,7 +18,7 @@

   constructor: ->

-    @path = ':channelName(:/postId)'
+    @path = ':channelName(/:postId)'
     @childRoutes = [
       new NewPublicChannelRoute
       new AllPublicChannelsRoute


I can just use the referrer to grab the query string

--- src/Carrot.coffee
+++ src/Carrot.coffee
@@ -28,7 +28,7 @@

   @trackLoad: () ->
     req = new XMLHttpRequest()
-    req.open 'GET', 'https://gocarrot.com/tracking?' + window?.document?.location?.search?.substr 1
+    req.open 'GET', 'https://gocarrot.com/tracking'
     req.send()

   constructor: (appId, udid, appSecret, hostname) ->


Emit disconnect only after updating internal state.

--- src/Client.coffee
+++ src/Client.coffee
@@ -130,13 +130,14 @@
         @_socket.on 'dropped', (count) => @_dropped count

     _disconnect: (err) =>
-        @emit 'disconnect', err
         @connected = false

         if @_socket?
             @_socket.close()
             @_socket?.removeAllListeners()
             @_socket = null
+
+        @emit 'disconnect', err

         if !@_closed and !@_connectTimer?
             @_connectTimer = setTimeout (


add support for convert vm into template

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -502,6 +502,22 @@
   full_copy: { type: 'boolean' }
 }

+exports.convert = ({id}) ->
+  @checkPermission 'admin'
+
+  try
+    VM = @getObject id
+  catch
+    @throw 'NO_SUCH_OBJECT'
+
+  xapi = @getXAPI VM
+  $wait xapi.call 'VM.set_is_a_template', VM.ref, true
+
+  return true
+exports.convert.params = {
+  id: { type: 'string' }
+}
+
 exports.snapshot = ({id, name}) ->
   @checkPermission 'admin'


chore(vm.set): fix some comments

chore(vm.set): fix some comments

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -534,10 +534,10 @@
   # Note: static_min ≤ dynamic_min ≤ dynamic_max ≤ static_max
   memory: { type: ['integer', 'string'], optional: true }

-  # Set static_min & dynamic_min
+  # Set dynamic_min
   memoryMin: { type: ['integer', 'string'], optional: true }

-  # Set static_max
+  # Set dynamic_max
   memoryMax: { type: ['integer', 'string'], optional: true }

   # Set static_max


Move modelSaved event prevention into Association classes

--- src/batman.coffee
+++ src/batman.coffee
@@ -2202,9 +2202,7 @@

       if associations = @constructor.associations
         {belongsTo, hasOne, hasMany} = associations.getObject()
-        # Prevent modelSaved for each association
-        for k in [belongsTo, hasOne, hasMany]
-          k?.forEach (key) => modelSaved.prevent()
+
         # Save belongsTo models immediately since we don't need this model to have an id
         belongsTo?.forEach (association) => association.save(@)

@@ -2319,6 +2317,8 @@
   save: (base) ->
     # saveEvent is allowed but not fired, because base's storage op still needs to happen
     saveEvent = base.event('modelSaved')
+    saveEvent.prevent()
+
     model = base.get(@label)
     if model?.state() is "dirty"
       model.save (err, record) =>
@@ -2368,6 +2368,8 @@

   save: (baseSaveError, base) ->
     saveEvent = base.event('modelSaved')
+    saveEvent.prevent()
+
     if relatedModel = base._batman.attributes?[@label]
       foreignKey = $functionName(base.constructor).toLowerCase() + "_id"
       relatedModel.set foreignKey, base.id
@@ -2422,6 +2424,8 @@
     saveEvent = base.event('modelSaved')
     if relatedModels = base._batman.attributes?[@label]
       relatedModels.forEach (model) =>
+        saveEvent.prevent()
+
         foreignKey = $functionName(base.constructor).toLowerCase() + "_id"
         model.set foreignKey, base.id


ServicePresence: implement failover url for services with ?all query

--- server/lib/server/services-presence.coffee
+++ server/lib/server/services-presence.coffee
@@ -20,6 +20,11 @@
   else
     no

+sendBody = (res, body)->
+  res.set 'Content-Type', 'application/json'
+  return res.send 200, body
+
+
 module.exports = (req, res, next) ->
   { params, query } = req

@@ -41,8 +46,18 @@

     request url, (error, response, body) ->
       if not error and response.statusCode is 200 and body
-        res.set 'Content-Type', 'application/json'
-        return res.send 200, body
+
+        # we can query for multiple services
+        if multi
+          try kontrolResponse = JSON.parse body
+
+          if Array.isArray kontrolResponse
+            if kontrolResponse.length isnt 0
+              return sendBody res, body
+
+        # if query is not multiple then
+        # no need for checking array length
+        else return sendBody res, body

       return  if failover req, res, multi
       return next()


multi-update

multi-update

--- coffee/main.coffee
+++ coffee/main.coffee
@@ -285,10 +285,8 @@
     setEndpointData: ->
       @props.endpoints[0].name = @endpoint[0].props.name
       @props.endpoints[0].sys = @endpoint[0].props.sys
-      #@props.endpoints[0].ifname = @endpoint[0].?
       @props.endpoints[1].name = @endpoint[1].props.name
       @props.endpoints[1].sys = @endpoint[1].props.sys
-      #@props.endpoints[1].ifname = @endpoint[1].?

     ifInternetToWanLink:  ->
       @applyWanProps() if @isInternet()
@@ -361,6 +359,7 @@
   addElement: (ef, x, y) ->
     e = new ef.constructor(@baseRect, x, y, 50)
     e.props.name = @ve.namemanager.getName(e.constructor.name.toLowerCase())
+    e.id.name = e.props.name
     e.props.design = dsg
     @elements.push(e)
     @ve.render()
@@ -671,8 +670,6 @@
     console.log(msg)

     $.post "/addie/"+dsg+"/design/update", JSON.stringify(msg), (data) =>
-      console.log("update post response")
-      console.log(data)
       x.id.name = x.props.name
       x.id.sys = x.props.sys

@@ -685,6 +682,30 @@
       updateLink(l) for l in x.links

     true
+
+  updates: (xs) =>
+    console.log("updating objects")
+    console.log(xs)
+
+    msg = { Elements: [] }
+
+    for x in xs
+      if x.shp?
+      x.props.position = x.shp.obj3d.position
+      ido = { OID: x.id, Type: x.constructor.name, Element: x.props }
+      msg.Elements.push(ido)
+
+      if x.links?
+        updateLink(l) for l in x.links
+
+      true
+
+    console.log(msg)
+
+    $.post "/addie/"+dsg+"/design/update", JSON.stringify(msg), (data) =>
+      for x in xs
+        x.id.name = x.props.name
+        x.id.sys = x.props.sys

 class EBoxSelectHandler


Add Google Analytics tracking code

Add Google Analytics tracking code

--- source/assets/javascripts/footer.js.coffee
+++ source/assets/javascripts/footer.js.coffee
@@ -23,3 +23,15 @@
 # $(document).ready Stellar
 $(window).load PositionElmts
 $(window).load Stellar
+
+# Google Analytics
+<script>
+  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
+
+  ga('create', 'UA-69304119-3', 'auto');
+  ga('send', 'pageview');
+
+</script>


Fix issue so an ellipse is immediately selectable. Cleanup mouse move tracking.

--- source/javascripts/elements/ellipse.coffee
+++ source/javascripts/elements/ellipse.coffee
@@ -3,28 +3,33 @@
     Grasp.Element.prototype.initialize.apply(this, arguments)

     @ellipse = @add @createEllipse()
-    @tracking = true

-    @canvas.on "mouse:move", (info) =>
-      if @tracking
-        offset = @canvas.el.offset()
+    @canvas.on "mouse:move", (info) => @resizeEllipse(info)
+    @canvas.on "mouse:up", (info) => @stopResize(info)

-        offsetX = info.e.pageX - offset.left
-        rx = (offsetX - @options.x) / 2
-        if rx > 0
-          @ellipse.left = @options.x + rx
-          @ellipse.rx = rx
+  stopResize: (info) ->
+    @canvas.off "mouse:move"
+    @canvas.off "mouse:up"

-        offsetY = info.e.pageY - offset.top
-        ry = (offsetY - @options.y) / 2
-        if ry > 0
-          @ellipse.top = @options.y + ry
-          @ellipse.ry = ry
+  resizeEllipse: (info) ->
+    offset = @canvas.el.offset()

-        @canvas.renderAll()
+    offsetX = info.e.pageX - offset.left
+    rx = (offsetX - @options.x) / 2
+    if rx > 0
+      @ellipse.left = @options.x + rx
+      @ellipse.rx = rx
+      @ellipse.width = rx * 2

-    @canvas.on "mouse:up", (info) =>
-      @tracking = false
+    offsetY = info.e.pageY - offset.top
+    ry = (offsetY - @options.y) / 2
+    if ry > 0
+      @ellipse.top = @options.y + ry
+      @ellipse.ry = ry
+      @ellipse.height = ry * 2
+
+    @ellipse.setCoords()
+    @canvas.renderAll()

   createEllipse: ->
     new fabric.Ellipse


Apply load spinner to #main instead of body There is no visible change but it seems better logically to do this.

--- src/mobile_opac.coffee
+++ src/mobile_opac.coffee
@@ -44,7 +44,7 @@

 		# We will apply a load spinner to the body,
 		# which will indicate that data is being transferred across the network.
-		$('body').load_spinner()
+		$('#main').load_spinner()

 		# We will prepare a container for enabling the user to log in or log out.
 		$('#login_bar').login_bar()


Fixed a bug causing projects to be added under the wrong user.

--- src/common.coffee
+++ src/common.coffee
@@ -23,8 +23,4 @@

     # Add the logged in user as the author when a doc is created in the collection.
     collection.before.insert (userId, doc) ->
-      if Meteor.isClient
-        username = Meteor.users.findOne(userId)?.username
-      else
-        username = 'admin'
-      doc.author = username
+      doc.author = Meteor.users.findOne(userId)?.username


Adding json to xml util method

Adding json to xml util method

--- src/main/webapp/lib/util/Util.coffee
+++ src/main/webapp/lib/util/Util.coffee
@@ -143,4 +143,145 @@
       test = _.filter(collection, (elem) -> elem.ident() is newId)
       if test?.length == 0
         return newId
-      newId+++      newId++
+
+  #parallel lines
+  parallelLines: (points, prj) ->
+    pPts = [] #left side of center
+
+    #shift the pts array away from the road
+    #o = (gapPx + weight)/2
+    offset = 0.00005
+    for i in [1..points.length-1]
+      p1 = prj.fromLatLngToPoint(points[i-1])
+      p2 = prj.fromLatLngToPoint(points[i])
+      theta = Math.atan2(p1.x-p2.x,p1.y-p2.y) + (Math.PI/2)
+      theta -= Math.PI*2 if(theta > Math.PI)
+      dx = offset * Math.sin(theta)
+      dy = offset * Math.cos(theta)
+      p1l = new google.maps.Point(p1.x+dx,p1.y+dy)
+      p2l = new google.maps.Point(p2.x+dx,p2.y+dy)
+      pPts.push(prj.fromPointToLatLng(p1l))
+
+    pPts.push(prj.fromPointToLatLng(p2l)) #last point
+    pPts
+
+  # this function computes the intersection of the sent lines p0-p1 and p2-p3
+  # and returns the intersection point,
+  intersect: (p0, p1, p2, p3) ->
+    # a1 b1 c1 a2 b2 c2 # constants of linear equations
+    # det_inv  # the inverse of the determinant of the coefficient matrix
+    # m1 m2    # the slopes of each line
+
+    x0 = p0.x;
+    y0 = p0.y;
+    x1 = p1.x;
+    y1 = p1.y;
+    x2 = p2.x;
+    y2 = p2.y;
+    x3 = p3.x;
+    y3 = p3.y;
+
+    # compute slopes, note the cludge for infinity, however, this will
+    # be close enough
+
+    if ((x1-x0)!=0)
+      m1 = (y1-y0)/(x1-x0)
+    else
+      m1 = 1e+10   # close enough to infinity
+
+    if ((x3-x2)!=0)
+      m2 = (y3-y2)/(x3-x2)
+    else
+      m2 = 1e+10   # close enough to infinity
+
+    #compute constants
+    a1 = m1
+    a2 = m2
+
+    b1 = -1
+    b2 = -1
+
+    c1 = (y0-m1*x0)
+    c2 = (y2-m2*x2)
+
+    #compute the inverse of the determinate
+    det_inv = 1/(a1*b2 - a2*b1)
+
+    # use Kramers rule to compute xi and yi
+    xi=((b1*c2 - b2*c1)*det_inv)
+    yi=((a2*c1 - a1*c2)*det_inv)
+
+    return new google.maps.Point(Math.round(xi),Math.round(yi))
+
+  # determine strokeweight for zoom
+  getLinkStrokeWeight: ->
+    zoomLevel = $a.map.getZoom()
+    if (zoomLevel >= 18)
+      newZoom = $a.Util.STROKE_WEIGHT_THICKER
+    else if (zoomLevel >= 17)
+      newZoom = $a.Util.STROKE_WEIGHT_THICK
+    else if (zoomLevel >= 16)
+      newZoom = $a.Util.STROKE_WEIGHT_THIN
+    else
+      newZoom = $a.Util.STROKE_WEIGHT_THINNER
+    newZoom
+
+  # Function to convert XML to JSON
+  # Reference: http://goessner.net/download/prj/jsonxml/
+  json2xml: (json, tab) ->
+    # Recursive function to transveres through child JSON objects
+    toXml: (v, name, ind) ->
+      # define XML variable to be global
+      $a.xml = "";
+      # If JSON element is an Array
+      if v instanceof Array
+        for elm in v
+          $a.xml += ind + toXml(elm, name, ind+"\t") + "\n"
+
+      # If JSON element is an object
+      else if typeof(v) == "object"
+        hasChild = false
+        $a.xml += ind + "<" + name
+        for key in v
+          # If element has "@" character at beginning it is an attribute
+          if key.charAt(0) == "@"
+            $a.xml += " " + key.substr(1) + "=\"" + v[key].toString() + "\""
+          # Otherwise element is a tag
+          else
+            hasChild = true
+        # Close brace dependent if it has children
+        $a.xml += if hasChild then ">" else "/>"
+
+        # Continue on if JSON element has child elements
+        if hasChild
+          for key in v
+            # If element key is of type text (no XML tag created)
+            if key == "#text"
+              $a.xml += v[key]
+            else if key == "#cdata"
+              $a.xml += "<![CDATA[" + v[key] + "]]>"
+            # Else if element is not an XML attribute it has children
+            else if key.charAt(0) != "@"
+              $a.xml += toXml v[key], m, ind+"\t"
+
+          # Add indentation for new element if it is on a new line
+          if $a.xml.charAt($a.xml.length-1) == "\n"
+            $a.xml += ind + "</" + name + ">"
+          else
+            $a.xml += "</" + name + ">"
+
+      # If JSON element is just a string value
+      else
+         $a.xml += ind + "<" + name + ">" + v.toString() +  "</" + name + ">"
+      $a.xml
+
+    for elm in json
+      xml += toXml(json[elm], elm, "");
+    if tab?
+      xml.replace(/\t/g, tab)
+    else
+      xml.replace(/\t|\n/g, "")
+    # return xml
+    xml
+


corrected precedence error in Graph::removeNode

--- src/lib/Graph.coffee
+++ src/lib/Graph.coffee
@@ -75,7 +75,7 @@

         @emit 'removeNode', node

-        if @nodes.indexOf node isnt -1
+        if -1 isnt @nodes.indexOf node
             @nodes.splice @nodes.indexOf(node), 1

     # ## Getting a node


Updated header docs for the Gulpfile.

Updated header docs for the Gulpfile.

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -6,26 +6,23 @@
 #
 # ## Tasks
 #
-#   * `gulp serve` (default)
+#   * `gulp serve` (or `npm start`) will start the development server and watch
+#     for changes to files in the src folder.
 #
 #   * `gulp compile` will compile all of the assets, scripts, styles and website
 #      into the `build` folder. The website may be viewed by opening the
 #      `build/website/index.html` file.
 #
-#   * `gulp test`
-#
-#   * `gulp test-browsers`
+#   * `gulp test` (or `npm test`) will run through our automated test suite in
+#     a headless browser (PhantomJS) and report the results on the console.
+#
+#   * `gulp test-browsers` will run through our automated test suite, just like
+#     `gulp test`, but it will run the suite in all of the installed browsers on
+#     your machine in addition to PhantomJS.
 #
 #   * `gulp distribute` will compile, optimize and package Cyclops for distribution
 #     in the `dist` folder. The current version in `package.json` will be used
 #     as the name of its containing folder in `dist`.
-#
-# ### Using With NPM
-#
-#   * `npm start`
-#   * `npm run compile`
-#   * `npm test`
-#   * `npm run dist`
 #

 # Dependencies -----------------------------------------------------------------


Fix SASS breaking when there is a syntax error

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -64,7 +64,7 @@
 gulp.task 'sass', ->
 	gulp.src [paths.sass]
 		.pipe plumber()
-		.pipe sass(outputStyle: 'compressed')
+		.pipe sass(outputStyle: 'compressed').on('error', sass.logError)
 		.pipe concat("#{outline.name}.min.css")
 		.pipe gulpif(args.prod, minifyCss())
 		.pipe gulp.dest("#{outline.dist}/css")


fixed little bugs

fixed little bugs

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -24,17 +24,16 @@

 banner = [
            "/**"
-           " * generator-git - Create the scaffolding for a new project."
-           " * @version v2.2.3"
-           " * @link    https://github.com/Kikobeats/generator-git"
-           " * @license MIT"
+           " * <%= pkg.name %> - <%= pkg.description %>"
+           " * @version v<%= pkg.version %>"
+           " * @link    <%= pkg.homepage %>"
+           " * @license <%= pkg.license %>"
            " */"].join("\n")

 # -- Tasks ---------------------------------------------------------------------

 gulp.task 'browserify', ->
-  browserify
-    extensions: ['.coffee', '.js']
+  browserify extensions: ['.coffee', '.js']
     .transform coffeeify
     .require(src.main, { expose: module.shortcut})
     .ignore('coffee-script')


Non-pipe tasks now only run once (dammit pipes)

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -91,15 +91,15 @@

 gulp.task 'browse', ['watch'], ->
 	startExpress()
-	gulp.src ANY_GLOB
+	gulp.src MAIN_SCRIPT
 		.pipe open '', url: "http://localhost:#{EXPRESS_PORT}/"

 gulp.task 'run-android', ['build'], ->
-	gulp.src ANY_GLOB
+	gulp.src MAIN_SCRIPT
 		.pipe exec 'pushd phonegap-build && phonegap run android && popd'

 gulp.task 'run-ios', ['build'], ->
-	gulp.src ANY_GLOB
+	gulp.src MAIN_SCRIPT
 		.pipe exec 'pushd phonegap-build && phonegap run ios && popd'

 gulp.task 'default', ['build']


fix botched kontrol-as-promised

fix botched kontrol-as-promised

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -76,7 +76,7 @@
   browserify
     entries     : ['./src/kontrol-as-promised/kontrol.coffee']
     debug       : no
-  .external './src/kite/kite.coffee'
+  .external './src/kite-as-promised/kite.coffee'
   .require './src/kontrol-as-promised/kontrol.coffee', expose: 'kontrol'
   .transform coffeeify
   .bundle()


github特殊符号处理：. 必须要3个以上

github特殊符号处理：. 必须要3个以上

--- lib/gitCreatePackage/gitCreatePackage-info-view.coffee
+++ lib/gitCreatePackage/gitCreatePackage-info-view.coffee
@@ -198,10 +198,12 @@

   checkName: (editorView)->
     str = editorView.getText()
-    regEx = /[\`\~\!\@\#\$\%\^\&\*\(\)\+\=\|\{\}\'\:\;\,\.\·\\\[\]\<\>\/\?\~\！\@\#\￥\%\…\…\&\*\（\）\—\—\+\|\{\}\【\】\‘\；\：\”\“\’\。\，\、\？]/g
+    regEx = /[\`\~\!\@\#\$\%\^\&\*\(\)\+\=\|\{\}\'\:\;\,\·\\\[\]\<\>\/\?\~\！\@\#\￥\%\…\…\&\*\（\）\—\—\+\|\{\}\【\】\‘\；\：\”\“\’\。\，\、\？]/g
     strcheck = str.replace(/[^\x00-\xff]/g,"-")
     strcheck = strcheck.replace(regEx,"-")
     strcheck = strcheck.replace(/-+/g, '-')
+    # 特殊处理
+    strcheck = '...' if strcheck is '.' or strcheck is '..'

     editorView.originalText = strcheck
     @warnPackageTextLabel.html("Will be created as #{strcheck}")


fix(crypto): all console.errors are now thrown errors

--- src/crypto.coffee
+++ src/crypto.coffee
@@ -2,8 +2,7 @@
 #
 generateKey = (password, salt) ->
   if password == ''
-    console.error 'Password for key generation empty!'
-    return false
+    throw new Error 'Password for key generation empty!'

   hex = sjcl.codec.hex.fromBits

@@ -24,7 +23,7 @@
 #
 generateSalt = (length = 2) ->
   rand = sjcl.random.randomWords length
-
+
   return sjcl.codec.hex.fromBits rand

 # Encrypt data with key
@@ -32,8 +31,7 @@
 #
 encryptData = (key, data, adata = '') ->
   if key == ''
-    console.error 'Encryption key empty!'
-    return false
+    throw new Error 'Encryption key empty!'

   options =
     mode : 'gcm'
@@ -50,8 +48,7 @@
 #
 decryptData = (key, data) ->
   if key == ''
-    console.error 'Decryption key empty!'
-    return false
+    throw new Error 'Decryption key empty!'

   options_ret = {}

@@ -65,4 +62,4 @@
     value = ''

   hash = sjcl.hash.sha256.hash value
-  return sjcl.codec.hex.fromBits hash+  return sjcl.codec.hex.fromBits hash


:fire: unused getPixelRect function

:fire: unused getPixelRect function

--- src/cursor.coffee
+++ src/cursor.coffee
@@ -653,9 +653,6 @@
     fn()
     @autoscroll() if options.autoscroll ? @isLastCursor()

-  getPixelRect: ->
-    @editor.pixelRectForScreenRange(@getScreenRange())
-
   getScreenRange: ->
     {row, column} = @getScreenPosition()
     new Range(new Point(row, column), new Point(row, column + 1))