Add reset method

Add reset method

--- framer/Components/NavComponent.coffee
+++ framer/Components/NavComponent.coffee
@@ -34,10 +34,7 @@

 		super options

-		@_stack = []
-		@_seen = []
-		@_current = null
-		@_isModal = false
+		@reset()

 		@overlay = new Layer
 			name: "overlay"
@@ -55,6 +52,15 @@
 				parent: @
 				width: @width
 				height: @height
+
+	reset: ->
+
+		@_stack = []
+		@_seen = []
+		@_current = null
+		@_isModal = false
+
+		@showNext(@_initial) if @_initial

 	# @define "isTransitioning",
 	# 	get: -> @_runningTransition
@@ -159,6 +165,7 @@

 	showNext: (layer, options={}) ->
+		@_initial ?= layer
 		@transition(layer, Transitions.show, options)

 	showPrevious: (options={}) =>


Add newline.

Add newline.

--- app/javascripts/imageSequence.coffee
+++ app/javascripts/imageSequence.coffee
@@ -16,4 +16,4 @@

       # Return the augmented image sequence.
       imageSequence
-  ]+  ]


:bug: remove using document object in render method due to not recognaize in server-side

--- src/renderer/components/doc-table-component.coffee
+++ src/renderer/components/doc-table-component.coffee
@@ -25,13 +25,9 @@
                 odd_even_style = if i % 2 == 1 then {} else {backgroundColor: '#F2F2F2'}
                 # alt_text = "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Incidunt libero, suscipit, rerum id ipsum provident voluptas deleniti dolor dignissimos nostrum, deserunt, vel voluptatem a. Nostrum rerum illum cum reiciendis quisquam! Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ipsa distinctio iure recusandae sapiente voluptatibus. Nobis corporis architecto numquam quibusdam, culpa quaerat voluptates, incidunt saepe dolore, velit distinctio placeat sequi accusantium."[0..Math.round(Math.random() * 478)]
                 alt_text = 'no description'
-                if @props.collapsed
-                  href = "#{document.location.pathname.match(/^(.+)\/[^\/]+$/)[1]}/#{child.name}"
-                else
-                  href = "#{document.location.pathname}/#{child.name}"

-                    <Link style={styles.link} href={href}>{child.name}</Link>
+                    <Link style={styles.link} uniqRoute={"class:local:.+?:#{@props.current.id}:#{child.id}"}>{child.name}</Link>

                   {
                     unless @props.collapsed


daily

daily

--- lib/asteroids/systems/collision_system.coffee
+++ lib/asteroids/systems/collision_system.coffee
@@ -16,21 +16,54 @@

 class asteroids.systems.CollisionSystem extends System #implements b2ContactListener

+  BulletHitAsteroid = 1
+  AsteroidHitShip = 2
+
+
   world           : null #  b2World
   creator         : null #  EntityCreator
   games           : null #  NodeList
-  que             : null #  collision que
+  collisions      : null #  collision que

   constructor: (@world, @creator) ->
-    @que = []
+    @collisions = []
     @world.SetContactListener(this)

   update:(time) =>
-    while @que.length
-      contact = @que.pop()
+    while @collisions.length
+      contact = @collisions.pop()
       switch (contact.type)
-        when 1 then @BulletHitAsteroid(contact.a, contact.b)
-        when 2 then @AsteroidHitShip(contact.a, contact.b)
+        when BulletHitAsteroid
+          do (bullet=contact.a, asteroid=contact.b) =>
+            if (bullet.get(Physics)?) # already been killed?
+              @creator.destroyEntity bullet
+              PhysicsSystem.deadPool.push(bullet.get(Physics).body)
+
+            if (asteroid.get(Physics)?) # already been killed?
+              radius = asteroid.get(Collision).radius
+              position = asteroid.get(Position).position
+              if (radius > 10)
+                @creator.createAsteroid(radius - 10, position.x + Math.random() * 10 - 5, position.y + Math.random() * 10 - 5)
+                @creator.createAsteroid(radius - 10, position.x + Math.random() * 10 - 5, position.y + Math.random() * 10 - 5)
+              body = asteroid.get(Physics).body
+              asteroid.get(Asteroid).fsm.changeState('destroyed')
+              asteroid.get(DeathThroes).body = body
+              #asteroid.get(Audio).play(ExplodeAsteroid)
+              if (@games.head)
+                @games.head.state.hits++
+            return
+
+        when AsteroidHitShip
+          do (asteroid=contact.a, spaceship=contact.b) =>
+            if (spaceship.get(Physics)?) # already been killed?
+              body = spaceship.get(Physics).body
+              spaceship.get(Spaceship).fsm.changeState('destroyed')
+              spaceship.get(DeathThroes).body = body
+              #asteroid.get(Audio).play(ExplodeShip)
+              if (@games.head)
+                @games.head.state.lives--
+
+            return
     return

   addToEngine: (engine) ->
@@ -42,43 +75,9 @@
     return # Void

   ###
-   * player hit asteroid
-  ###
-  BulletHitAsteroid: (bullet, asteroid) =>
-    if (bullet.get(Physics)?) # already been killed?
-      @creator.destroyEntity bullet
-      PhysicsSystem.deadPool.push(bullet.get(Physics).body)
-
-    if (asteroid.get(Physics)?) # already been killed?
-      radius = asteroid.get(Collision).radius
-      position = asteroid.get(Position).position
-      if (radius > 10)
-        @creator.createAsteroid(radius - 10, position.x + Math.random() * 10 - 5, position.y + Math.random() * 10 - 5)
-        @creator.createAsteroid(radius - 10, position.x + Math.random() * 10 - 5, position.y + Math.random() * 10 - 5)
-      body = asteroid.get(Physics).body
-      asteroid.get(Asteroid).fsm.changeState('destroyed')
-      asteroid.get(DeathThroes).body = body
-      #asteroid.get(Audio).play(ExplodeAsteroid)
-      if (@games.head)
-        @games.head.state.hits++
-    return
-
-  ###
-   * asteroid hit spaceship
-  ###
-  AsteroidHitShip: (asteroid, spaceship) =>
-    if (spaceship.get(Physics)?) # already been killed?
-      body = spaceship.get(Physics).body
-      spaceship.get(Spaceship).fsm.changeState('destroyed')
-      spaceship.get(DeathThroes).body = body
-      #asteroid.get(Audio).play(ExplodeShip)
-      if (@games.head)
-        @games.head.state.lives--
-
-    return
-
-  ###
    * b2ContactListener Interface
+   *
+   * filter the event and collisions up valid combinations
   ###
   BeginContact: (contact) =>

@@ -88,20 +87,17 @@
     switch (a.type)
       when 'asteroid'
         switch(b.type)
-          when 'asteroid'   then return
-          when 'bullet'     then return @que.push(type: 1, a: b.entity, b: a.entity)
-          when 'spaceship'  then return @que.push(type: 2, a: a.entity, b: b.entity)
+          when 'bullet'
+            @collisions.push(type: BulletHitAsteroid, a: b.entity, b: a.entity)
+          when 'spaceship'
+            @collisions.push(type: AsteroidHitShip, a: a.entity, b: b.entity)
       when 'bullet'
-        switch(b.type)
-          when 'asteroid'   then return @que.push(type: 1, a: a.entity, b: b.entity)
-          when 'bullet'     then return
-          when 'spaceship'  then return
+        if (b.type is 'asteroid')
+          @collisions.push(type: BulletHitAsteroid, a: a.entity, b: b.entity)

       when 'spaceship'
-        switch(b.type)
-          when 'asteroid'   then return @que.push(type: 2, a: b.entity, b: a.entity)
-          when 'bullet'     then return
-          when 'spaceship'  then return
+        if (b.type is 'asteroid')
+          @collisions.push(type: AsteroidHitShip, a: b.entity, b: a.entity)
     return
     ###
      * type:


Fixed stream listeners for node 0.8.x

Fixed stream listeners for node 0.8.x

--- src/io.coffee
+++ src/io.coffee
@@ -21,6 +21,7 @@
       deferred = When.defer()
       stream = fs.createWriteStream(filename)
         .once('error', deferred.reject)
+        .once('close', deferred.resolve)
         .once('finish', deferred.resolve)
       @source.pipe stream
       deferred.promise


axis buffers only get added when the axis is reset

--- src/viz/types/helpers/graph/includes/buffer.coffee
+++ src/viz/types/helpers/graph/includes/buffer.coffee
@@ -3,7 +3,7 @@

 module.exports = (vars, axis, buffer) ->

-  if vars[axis].scale.value isnt "share" and !vars[axis].range.value
+  if vars[axis].scale.value isnt "share" and !vars[axis].range.value and vars[axis].reset

     if axis is vars.axes.discrete


fix query function

fix query function

--- assets/coffeescript/pupil_store.coffee
+++ assets/coffeescript/pupil_store.coffee
@@ -679,24 +679,28 @@
         document.location = "?" + $.param(data) + '&user=uk' + '&test=1'

   eventFillORderFormFromQueryString: ->
-    query = window.location.search.substring(1)
-
-    if query.length > 0
-      if testQuery == '1'
-        formInput = $('#order-form').find(':input')
-        for input in formInput
-          if $(input).prop('type') == 'checkbox'
-            if $(input).attr('id') == 'q_request'
-              $(input).prop(countryObj[$(input).prop('id')], true)
-              $("span[name='#{$(input).prop('id')}']").addClass('checkmark--active')
+    if $(@cartPage).length > 0
+      query = window.location.search.substring(1)
+      urlParams = new URLSearchParams(window.location.search)
+      testQuery = urlParams.get('test')
+      userQuery = urlParams.get('user')
+
+      if query.length > 0
+        if testQuery == '1'
+          formInput = $('#order-form').find(':input')
+          for input in formInput
+            if $(input).prop('type') == 'checkbox'
+              if $(input).attr('id') == 'q_request'
+                $(input).prop(getDummyData(userQuery)[$(input).prop('id')], true)
+                $("span[name='#{$(input).prop('id')}']").addClass('checkmark--active')
+              else
+                $("span[name='#{$(input).prop('id')}']").addClass('checkmark--active')
+                $(".Form-shipping-container").fadeIn(250)
             else
-              $("span[name='#{$(input).prop('id')}']").addClass('checkmark--active')
-              $(".Form-shipping-container").fadeIn(250)
-          else
-            $(input).val(countryObj[$(input).prop('name')])
-        console.log('Test Success')
-      else
-        console.log('Test failed')
+              $(input).val(getDummyData(userQuery)[$(input).prop('name')])
+          console.log('Test Success')
+        else
+          console.log('Test failed')

   eventFillCartFromQueryString: ->
     query = window.location.search.substring(1)


engine data module

engine data module

--- test/engine_data.coffee
+++ test/engine_data.coffee
@@ -4,10 +4,6 @@
   engine = Engine
     backgroundColor: false

-  3.times (n) ->
-    engine.delay n, ->
-      ok(true)
-
   engine.frameAdvance()

 module()


Fix syntax

Fix syntax

--- lib/space-pen-extensions.coffee
+++ lib/space-pen-extensions.coffee
@@ -42,7 +42,7 @@
     if grammar = atom.syntax.grammarForScopeName(grammarScopeName)
       refreshHtml(grammar)
     else
-      syntax.on 'grammar-added grammar-updated', (grammar) ->
+      atom.syntax.on 'grammar-added grammar-updated', (grammar) ->
         return unless grammar.scopeName == grammarScopeName
         refreshHtml(grammar)


bugfix for memory chunk size

bugfix for memory chunk size

--- libraries/metrics/memory.coffee
+++ libraries/metrics/memory.coffee
@@ -8,11 +8,11 @@

 CpuTimeBucket = 100 # current cpu time allowance in milliseconds
 CpuTimeBucketMax = 100 # maximum amount of cpu time allowed in bucket
-CpuTimeBucketRate = 1 # add this many milliseconds per minute
+CpuTimeBucketRate = 10 # add this many milliseconds per minute

 gcInterval = 1 # how many minutes between gc (parameter is dynamically adjusted)
 countSinceLastGc = 0 # how many minutes since last gc
-MemoryChunkSize = 4 * oneMegaByte # how much we need to free to consider gc worth doing
+MemoryChunkSize = 4 # how many megabytes we need to free to consider gc worth doing

 readyToGc = () ->
 	# update allowed cpu time


upgrade compilation task

upgrade compilation task

--- library/nucleus/cakefile.coffee
+++ library/nucleus/cakefile.coffee
@@ -30,6 +30,8 @@
 logger = require "winston"

 _ = require "lodash"
+{minify} = require "uglify-js"
+{readdirSyncRecursive} = require "wrench"
 {rmdirSyncRecursive} = require "wrench"
 {spawn} = require "child_process"
 {puts} = require "util"
@@ -107,7 +109,13 @@
         assert _.isObject compiler.on "exit", (status) ->
             failure = "Failed to compile framework library"
             success = "Compiled framework library successfuly"
+            produce = "Produce compiled artifact %s/%s".cyan
             return logger.error failure.red if status isnt 0
+            assert s = (xstats) -> return xstats.isDirectory()
+            isDirS = (d) -> (p) -> s fs.lstatSync("#{d}/#{p}")
+            assert sources = try readdirSyncRecursive artifacts
+            assert sources = _.reject sources, isDirS artifacts
+            logger.info produce, artifacts, s for s in sources
             logger.info success.green if status is 0

     # This task launches an instance of application where this task


Revert "Ignore mutations on parent-less nodes"

This reverts commit 84e57c05ef587896b40e954a5c57833d32ad7abc.

a) I don't remember what issue with attachment caption editing this fixed.
b) This causes us to miss significant mutations leading to a bad render state.

--- src/trix/observers/mutation_observer.coffee
+++ src/trix/observers/mutation_observer.coffee
@@ -44,7 +44,7 @@
     false

   nodeIsSignificant: (node) ->
-    node?.parentNode and node isnt @element and not @nodeIsMutable(node)
+    node isnt @element and not @nodeIsMutable(node)

   nodeIsMutable: (node) ->
     findClosestElementFromNode(node, matchingSelector: mutableSelector)


allow changing state if current is in t.from

--- src/plugins/scaleApp.state.coffee
+++ src/plugins/scaleApp.state.coffee
@@ -77,7 +77,7 @@
   can: (id) ->
     t = @transitions[id]
     t?.from is @current or
-    @current in t or
+    @current in t.from or
     t.from is "*"

 plugin =


removing unused functions

removing unused functions

--- src/scripts/summer/actions.coffee
+++ src/scripts/summer/actions.coffee
@@ -1,12 +1,4 @@
 'use strict';
-
-generateActionName = (namespace, action) ->
-  "#{namespace}.#{action}"
-
-generateListenables = (namespace, actions) ->
-  actions.map (action) ->
-    generateActionName(namespace, action)
-

 createActions = (namespace, Dispatcher, actions) ->
   Actions = () ->


compute-server: do not use sudo if already root, to avoid messing up the environment (this matters for cocalc-kubernetes)

--- src/smc-hub/compute-server.coffee
+++ src/smc-hub/compute-server.coffee
@@ -38,6 +38,7 @@

 net         = require('net')
 fs          = require('fs')
+os          = require('os')

 async       = require('async')
 winston     = require('winston')
@@ -68,13 +69,13 @@

 if process.env.SMC_STORAGE?
     STORAGE = process.env.SMC_STORAGE
-else if misc.startswith(require('os').hostname(), 'compute')   # my official deploy: TODO -- should be moved to conf file.
+else if misc.startswith(os.hostname(), 'compute')   # my official deploy: TODO -- should be moved to conf file.
     STORAGE = 'storage0-us'
 else
     STORAGE = ''
     # TEMPORARY:

-
+USERNAME = os.userInfo().username

 #################################################################
 #
@@ -97,8 +98,12 @@
         v = ['--dev', "--projects", PROJECT_PATH]
     else
         winston.debug("smc_compute: running #{misc.to_safe_str(opts.args)}")
-        command = "sudo"
-        v = ["/usr/local/bin/smc-compute"]
+        if USERNAME != 'root'
+            command = "sudo"
+            v = ["/usr/local/bin/smc-compute"]
+        else
+            command = '/usr/local/bin/smc-compute'
+            v = []
     if program.single
         v.push("--single")
     if program.kubernetes
@@ -1034,9 +1039,17 @@
     if opts.command == 'outgoing' and NO_OUTGOING_FIREWALL
         opts.cb()
         return
+    firewall_cmd = "#{process.env.SALVUS_ROOT}/scripts/smc_firewall.py"
+    if USERNAME != 'root'
+        command = 'sudo'
+        args = [firewall_cmd, opts.command].concat(opts.args)
+    else
+        command = firewall_cmd
+        args = [opts.command].concact(opts.args)
+
     misc_node.execute_code
-        command : 'sudo'
-        args    : ["#{process.env.SALVUS_ROOT}/scripts/smc_firewall.py", opts.command].concat(opts.args)
+        command : command
+        args    : args
         bash    : false
         timeout : 30
         path    : process.cwd()


Revert Checkbox behavior

Revert Checkbox behavior

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -116,7 +116,7 @@
         @props.actions.set_most_recent_file_click(full_name)

     render: ->
-        e.stopPropagation()} onMouseUp={(e)=>e.stopPropagation()} style={@props.style}>
+
             <Icon name={if @props.checked then 'check-square-o' else 'square-o'} fixedWidth style={fontSize:'14pt'}/>


code cleanup

code cleanup

--- src/textwrap/helpers/tspan.coffee
+++ src/textwrap/helpers/tspan.coffee
@@ -14,9 +14,8 @@
       .attr "x", x + "px"
       .attr "dx", dx + "px"
       .attr "dy", dy + "px"
-      # .attr "dy", if first and valign is "top" then "0px" else dy + "px"
       .style "baseline-shift", "0%"
-      .style "dominant-baseling", "alphabetical"
+      .style "dominant-baseline", "alphabetical"
       .text w

   mirror = vars.rotate.value is -90 or vars.rotate.value is 90


(fix) Adapting height to graph size.

(fix) Adapting height to graph size.

--- src/dot_processor.coffee
+++ src/dot_processor.coffee
@@ -14,7 +14,9 @@
         id = "dg-" + uuid.v4()
         postProcessors.registerElemenbById id, (elem, done) ->
             d3.select(elem).call(render, graph)
-            elem.height = elem.getElementsByClassName('output')[0]?.getBoundingClientRect().height + 22
+            svgElem = elem.getElementsByClassName('output')[0]
+            svgHeight = svgElem?.getBoundingClientRect().height || 0
+            elem.style.height = svgHeight + 22

             # remove arrows in undirected graphs
             if !graph.isDirected()


Fix label helper

Fix label helper

--- assets/scripts/app/helpers/handlebars.coffee
+++ assets/scripts/app/helpers/handlebars.coffee
@@ -48,7 +48,6 @@
     options.hash.for = id
     options.hashTypes.for = 'STRING'
     options.hashContexts.for = this
-    options.fn = Ember.Handlebars.compile("{{view.content}}")

   Ember.Handlebars.helpers.view.call(this, view, options)
 )


Remove Ext.Component's special handling in Deft.Injector

--- packages/deft/src/coffee/ioc/Injector.coffee
+++ packages/deft/src/coffee/ioc/Injector.coffee
@@ -232,16 +232,11 @@
 			for name, value of injectConfig
 				setterFunctionName = 'set' + Ext.String.capitalize( name )
 				targetInstance[ setterFunctionName ].call( targetInstance, value )
-		else
-			if targetInstance instanceof Ext.ClassManager.get( 'Ext.Component' )
-				# NOTE: Ext JS's Ext.Component doesn't "play by the rules" and never actually calls Ext.Base::initConfig().
-				# Store the configs to be injected and apply via the constructor override define below.
-				targetInstance.injectConfig = injectConfig
-			else if Deft.isFunction( targetInstance.initConfig )
-				originalInitConfigFunction = targetInstance.initConfig
-				targetInstance.initConfig = ( config ) ->
-					result = originalInitConfigFunction.call( @, Ext.Object.merge( {}, config or {}, injectConfig ) )
-					return result
+		else if Deft.isFunction( targetInstance.initConfig )
+			originalInitConfigFunction = targetInstance.initConfig
+			targetInstance.initConfig = ( config ) ->
+				result = originalInitConfigFunction.call( @, Ext.Object.merge( {}, config or {}, injectConfig ) )
+				return result

 		return targetInstance
 ,


groups: improve code style in `JGroup::create` method

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -495,15 +495,15 @@
           unless subscription?
             return callback message: "Subscription required!"

-          subscription.checkUsage pack, (err, nonce) =>
+          subscription.checkUsage pack, (err) =>
             return callback err  if err

-            @create formData, delegate, (err, group) =>
+            @create formData, delegate, (err, group) ->
               return callback err  if err

               debitOptions = {
                 pack
-                # avoid creating a nonce, we'll debit the subscription and
+                # avoid creating a nonce, we'll debit the subscription and
                 # create the group all at once.
                 shouldCreateNonce: no
               }


need to do this the slow way

need to do this the slow way

--- workers/social/lib/social/traits/slugifiable.coffee
+++ workers/social/lib/social/traits/slugifiable.coffee
@@ -1,6 +1,6 @@
 module.exports = class Slugifiable

-  {dash} = require 'bongo'
+  {dash, daisy} = require 'bongo'

   slugify =(str)->
     slug = str
@@ -10,6 +10,7 @@
       .replace(/[^a-z0-9-]+/g, "")  # remove all non-alphanumeric characters except the hyphen
       .replace(/[-]+/g, "-")        # replace multiple instances of the hyphen with a single instance
       .replace(/^-+|-+$/g, "")      # trim leading and trailing hyphens
+      .substr 0, 256                # limit these to 256-chars (pre-suffix), for sanity

   generateUniqueSlug =(constructor, slug, i, callback)->
     candidate = "#{slug}#{i or ''}"
@@ -23,27 +24,27 @@
   @updateAllSlugs = (options, callback)->
     [callback, options] = [options, callback] unless callback
     options ?= {}
-    selector = if options.force then {} else {slug: $exists: no}
+    selector = if options.force then {} else {slug_: $exists: no}
     subclasses = @encapsulatedSubclasses ? [@]
     contentTypeQueue = subclasses.map (subclass)->->
       subclass.cursor selector, options, (err, cursor)->
-        if err then contentTypeQueue.fin err
+        if err then contentTypeQueue.next err
         else
           postQueue = []
           cursor.each (err, post)->
-            if err then postQueue.fin err
+            if err then postQueue.next err
             else if post?
               postQueue.push -> post.updateSlug (err, slug)->
                 callback null, slug
-                postQueue.fin()
+                postQueue.next()
             else
-              dash postQueue, -> contentTypeQueue.fin()
+              daisy postQueue, -> contentTypeQueue.fin()
     dash contentTypeQueue, callback

   updateSlug:(callback)->
     @createSlug (err, slug)=>
       if err then callback err
-      else @update $set:{slug}, (err)->
+      else @update $set:{slug, slug_:slug}, (err)->
         callback err, unless err then slug

   createSlug:(callback)->


Utils: Added createExternalLink method.

Utils: Added createExternalLink method.

--- core/utils.coffee
+++ core/utils.coffee
@@ -124,6 +124,14 @@
         if hljs.LANGUAGES[lang]?
         then hljs.highlight(lang,text).value
         else text
+
+  createExternalLink: (href) ->
+    tag = document.createElement "a"
+    tag.href = if href.indexOf("http") > -1 then href else "http://#{href}"
+    tag.target = "_blank"
+    document.body.appendChild tag
+    tag.click()
+    document.body.removeChild tag

   wait: (duration, fn)->
     if "function" is typeof duration


utils: fix implementation of .extend

utils: fix implementation of .extend

--- core/utils.coffee
+++ core/utils.coffee
@@ -8,9 +8,8 @@
   idCounter : 0

   extend:(obj)->
-    for key, val of obj
-      if @[key] then throw new Error "#{key} is already registered"
-      else @[key] = val
+    @[key] = val  for key, val of obj
+    return obj

   formatPlural:(count, noun, showCount = yes)->
     """


error recovery: alert user if vm fails to turn on/timesout

--- core/utils.coffee
+++ core/utils.coffee
@@ -447,6 +447,12 @@

     kallback.cancel =-> clearTimeout fallbackTimer
     kallback
+
+  notifyAndEmailVMTurnOnFailureToSysAdmin: (vmName)->
+    new KDNotificationView
+      title:"Sorry, your vm failed to turn on. An email has been sent to a sysadmin."
+
+    KD.whoami().sendEmailVMTurnOnFailureToSysAdmin vmName

   logTimeoutToExternal: (timerName)->
     KD.logToMixpanel timerName+".timeout"


Add support for templateHelpers

Add support for templateHelpers

--- src/scripts/helpers/backbone/views/attached/tooltip/tooltip.coffee
+++ src/scripts/helpers/backbone/views/attached/tooltip/tooltip.coffee
@@ -15,11 +15,11 @@
     trigger: 'hover'
     placement: 'top'

-    _renderDom: (data) ->
+    renderDom: (data) ->
       @$el?.html @containerTemplate
         title: @title
         placement: @placement
-        content: @template?(data) or @template
+        content: @getTemplate()

     initialize: (options = {}) ->
       super()


hotfix for #542 - remove offending reload to prevent safari from endlessly loading

--- app/assets/javascripts/search/presenter.coffee
+++ app/assets/javascripts/search/presenter.coffee
@@ -82,8 +82,6 @@
   CALLBACKS:
     document:
       'Clarat.Location::NewLocation': 'handleNewGeolocation'
-    window:
-      popstate: 'handlePopstate'
     '#search_form_query':
       keyup: 'handleQueryKeyUp'
       change: 'handleQueryChange'
@@ -214,11 +212,6 @@
     $('.JS-EncounterSelector').each ->
       $(@).attr 'disabled', true

-  handlePopstate: =>
-    window.location = window.location
-    # TODO: for more performance we could load from the event.state instead of
-    #       reloading
-
   ### Non-event-handling private methods ###

   getNestedData: (eventTarget, selector, elementName) ->


fixed typo

fixed typo

--- app/adapters/payment-subscription.coffee
+++ app/adapters/payment-subscription.coffee
@@ -2,7 +2,7 @@
 `import ENV from '../config/environment'`
 `import {SimwmsHeaders, Singleton} from 'simwms-shared'`

-PaymentSubscriptionAdapter = ActiveModelAdapter.extend SimwmsHeader, Singleton,
+PaymentSubscriptionAdapter = ActiveModelAdapter.extend SimwmsHeaders, Singleton,
   host: ENV.host
   namespace: ENV.apiaNamespace


fix filerev

fix filerev

--- lib/grunt/task_helpers/filerev/filerev.coffee
+++ lib/grunt/task_helpers/filerev/filerev.coffee
@@ -63,6 +63,8 @@

   createRevisionFiles: (files) ->
     _.each files, (path) =>
+      return unless @grunt.file.isFile(path)
+
       if @withDigest() == true
         newPath = @_digestPath(path)
         @grunt.file.copy(path, newPath)


More MySQL updates

More MySQL updates

--- src/coffee/orms/mysql.coffee
+++ src/coffee/orms/mysql.coffee
@@ -59,8 +59,8 @@
    processFile: (client, sqlFile, success, failure) ->
       client.query sqlFile,
          (err, result) ->
-            failure err, client, mysqlDatabaseHandler if err and failure
+            return failure err, client, mysqlDatabaseHandler if err and failure
             process.stdout.write ' Successful'.green
-         success client
+            success client

 module.exports = mysqlDatabaseHandler;


BUG: added explicit 'return' in main-controller to avoid returning DOM node in controller

--- app/scripts/controllers/main-controller.coffee
+++ app/scripts/controllers/main-controller.coffee
@@ -37,5 +37,9 @@
       # Set 'current' property to true, which will add css class 'current'
       $scope.$apply ->
         headerCurrentOption.current = true
+
+      return
+
+    return
   ]


Fix percent voted number

Fix percent voted number

--- angular/core/models/poll_model.coffee
+++ angular/core/models/poll_model.coffee
@@ -103,7 +103,7 @@
       @stancesCount + @undecidedCount

     percentVoted: ->
-      return 0 if @undecidedUserCount == 0
+      return 0 if @membersCount() == 0
       (100 * @stancesCount / (@membersCount())).toFixed(0)

     outcome: ->


Return also admins without write access from route

--- app/controllers/league_controller.coffee
+++ app/controllers/league_controller.coffee
@@ -92,7 +92,7 @@
       .catch (error) -> response.status(500).json(error: error)

   router.get '/:leagueId/admins', (request, response) ->
-    request.league.getAdmins(where: {write: true})
+    request.league.getAdmins()
       .then (admins) ->
         response.json(admins)
       .catch (error) -> response.status(500).json(error: error)


Fix qunits

Fix qunits


Changed: update Atom config

Changed: update Atom config

--- dotfiles/atom/config.cson
+++ dotfiles/atom/config.cson
@@ -153,6 +153,7 @@
   "tree-view":
     showOnRightSide: true
     hideIgnoredNames: true
+    hideVcsIgnoredFiles: true
   "find-and-replace":
     openProjectFindResultsInRightPane: true
     focusEditorAfterSearch: true
@@ -168,3 +169,4 @@
     microinterval: 30
     intervalcount: 1
     break: 1
+  pigments: {}


fix(minify task): cleanup-client moveTempTask() for windows via setting the base tag for gulp

--- src/tasks/clean/cleanup-client.coffee
+++ src/tasks/clean/cleanup-client.coffee
@@ -45,10 +45,11 @@
 			message: msg

 	moveTempTask = (msg) ->
-		defer = q.defer()
-		src   = config.temp.client.glob
-		dest  = config.dist.app.client.dir
-		gulp.src src
+		defer   = q.defer()
+		src     = config.temp.client.glob
+		dest    = config.dist.app.client.dir
+		srcOpts = base: config.temp.client.dir
+		gulp.src src, srcOpts
 			.on 'error', (e) -> defer.reject e
 			.pipe gulp.dest dest
 			.on 'end', ->


En vez de seleccionar la primera versión, seleccionamos la penúltima.

La referencia que sigo es la wikipedia, que lo hace así.

--- app/scripts/seleccion_diferencias.coffee
+++ app/scripts/seleccion_diferencias.coffee
@@ -21,5 +21,5 @@
             $(this).show()
           else
             $(this).hide()
-  $('input:radio[name="ref_id"]:last').attr('checked', 'checked').change()
+  $('input:radio[name="ref_id"]:gt(0):first').attr('checked', 'checked').change()
   $('input:radio[name="version_id"]:first').attr('checked', 'checked').change()


MainCam : new algorith implemented to calculate size of zoomCams

--- app/static/scripts/modules/camera.coffee
+++ app/static/scripts/modules/camera.coffee
@@ -36,7 +36,6 @@
             video.muted = yes
             @newStream event, { video : video , uid : window.Voicious.currentUser._id , local : yes }
         ($ window).on 'resize', () =>
-            do @squareMainCam
             videos = ($ 'video')
             for video in videos
                 @centerVideoTag ($ video)
@@ -49,10 +48,6 @@
             video = (mainLi.find 'video')
             if video?
                 @zoom (video.attr 'rel'), video
-        do @squareMainCam
-
-    squareMainCam : () =>
-        @jqMainCams.width do @jqMainCams.height

     delStream   : (event, user) =>
         if (@streams.indexOf user.id) >= 0
@@ -83,12 +78,23 @@

     resizeZoomCams : () =>
         cam = ($ '#mainCam')
-        val = @mosaicNb * @mosaicNb
-        if val < Object.keys(@zoomCams).length
-            @mosaicNb += 1
-        else if @mosaicNb > Object.keys(@zoomCams).length
-            @mosaicNb -= 1
-        size = ((do cam.width) / @mosaicNb) - 10 # ugly fix
+        x = do cam.width
+        y = do cam.height
+        n = Object.keys(@zoomCams).length
+        px = Math.ceil(Math.sqrt(n * x / y))
+        if n is 0
+            return
+        if Math.floor(px * y / x) * px < n
+            sx = y / Math.ceil(px * y / x)
+        else
+            sx = x / px
+
+        py = Math.ceil(Math.sqrt(n * y/ x))
+        if Math.floor(py * x / y) * py < n
+            sy = x / Math.ceil(x * py / y)
+        else
+            sy = y / py
+        size = if sx > sy then sx else sy
         for key, li of @zoomCams
             li.css 'width', "#{size}px"
             li.css 'height', "#{size}px"


updated watch to watch js

updated watch to watch js

--- app/templates/grunt/contrib-watch.coffee
+++ app/templates/grunt/contrib-watch.coffee
@@ -17,8 +17,10 @@
         '<%%= compass.dev.options.sassDir %>/**/*.scss'
       <% } %>]
       tasks: [
-        'compass:dev'
-      ]
+        <% if (precompilerSass) { %>
+        'compass:devSASS'<% } %><% if (precompilerScss) { %>
+        'compass:devSCSS'
+      <% } %>]
     <% } %>css:
       files: [
         'assets/css/*.css'
@@ -39,6 +41,17 @@
         livereload: true
         spawn: false
     <% } %>
+    <% } %><% if (precompilerJS) { %>js:
+      files: [
+        'assets/javascript/**/*.js'
+      ]
+      tasks: [
+        'jshint'
+        'jscs'
+      ]
+      options:
+        livereload: true
+        spawn: false
     <% } %><% if (precompilerLess) { %>less:
       files: [
         '<%%= less.dev.options.paths[0] %>/**/*.less'
@@ -64,7 +77,8 @@
   <% if (precompilerCoffee || precompilerJade) { %>changedFiles = {}
   onChange = grunt.util._.debounce () ->
     <% if (precompilerCoffee) { %>changedCoffeeFiles = changedFiles['coffee']
-    <% } %><% if (precompilerJade) { %>changedJadeFiles = changedFiles['jade']<% } %>
+    <% } %><% if (precompilerJade) { %>changedJadeFiles = changedFiles['jade']
+    <% } %><% if (precompilerJS) { %>changedJSFiles = changedFiles['js']<% } %>

     <% if (precompilerCoffee) { %>if changedCoffeeFiles
       grunt.config 'coffeelint.all', changedCoffeeFiles
@@ -74,6 +88,9 @@
     if changedJadeFiles
       grunt.config 'jade.dev.files',
         './': changedJadeFiles
+    <% } %><% if (precompilerJS) { %>if changedJSFiles
+      grunt.config 'jshint.all', changedJSFiles
+      grunt.config 'jscs.src', changedJSFiles
     <% } %>
     changedFiles = {}
   , 200


Client: Activity: Added CollaborationComingSoonModal and StartVideoCallLink components and functionality for PrivateMessageThreadPane component

--- client/activity/lib/components/privatemessagethreadpane/index.coffee
+++ client/activity/lib/components/privatemessagethreadpane/index.coffee
@@ -1,15 +1,17 @@
-kd                        = require 'kd'
-React                     = require 'kd-react'
-KDReactorMixin            = require 'app/flux/reactormixin'
-ActivityFlux              = require 'activity/flux'
-immutable                 = require 'immutable'
-classnames                = require 'classnames'
-PrivateChatPane           = require 'activity/components/privatechatpane'
-ThreadSidebarContentBox   = require 'activity/components/threadsidebarcontentbox'
-ChannelParticipantAvatars = require 'activity/components/channelparticipantavatars'
-ThreadSidebar             = require 'activity/components/threadsidebar'
-prepareThreadTitle        = require 'activity/util/prepareThreadTitle'
-ImmutableRenderMixin      = require 'react-immutable-render-mixin'
+kd                           = require 'kd'
+React                        = require 'kd-react'
+KDReactorMixin               = require 'app/flux/reactormixin'
+ActivityFlux                 = require 'activity/flux'
+immutable                    = require 'immutable'
+classnames                   = require 'classnames'
+PrivateChatPane              = require 'activity/components/privatechatpane'
+ThreadSidebarContentBox      = require 'activity/components/threadsidebarcontentbox'
+ChannelParticipantAvatars    = require 'activity/components/channelparticipantavatars'
+ThreadSidebar                = require 'activity/components/threadsidebar'
+prepareThreadTitle           = require 'activity/util/prepareThreadTitle'
+ImmutableRenderMixin         = require 'react-immutable-render-mixin'
+StartVideoCallLink           = require 'activity/components/common/startvideocalllink'
+CollaborationComingSoonModal = require 'activity/components/collaborationcomingsoonmodal'

 module.exports = class PrivateMessageThreadPane extends React.Component
@@ -36,12 +38,23 @@
       channelThread         : immutable.Map()
       channelThreadMessages : immutable.List()
       channelParticipants   : immutable.List()
+      isComingSoonModalOpen : no

   componentDidMount: -> reset @props, @state

   componentWillReceiveProps: (nextProps) -> reset nextProps, @state
+
+
+  startVideoCall: ->
+
+    @setState isComingSoonModalOpen: yes
+
+
+  onClose: ->
+
+    @setState isComingSoonModalOpen: no

   renderHeader: ->
@@ -61,9 +74,13 @@

   render: ->

+      <CollaborationComingSoonModal
+        onClose={@bound 'onClose'}
+        isOpen={@state.isComingSoonModalOpen}/>

           {@renderHeader()}
+          <StartVideoCallLink startVideoCall={@bound 'startVideoCall'}/>


webterm: focus issues (on click, right click, paste and copy) fixed

--- client/landing/app/Applications/WebTerm.kdapplication/AppView.coffee
+++ client/landing/app/Applications/WebTerm.kdapplication/AppView.coffee
@@ -106,6 +106,7 @@

     $(document).on "paste", (event) =>
       @terminal.server.input event.originalEvent.clipboardData.getData("text/plain") if @focused
+      @setKeyView()

     @bindEvent 'contextmenu'

@@ -147,6 +148,7 @@
   contextMenu: (event) ->
     # invisible textarea will be placed under the cursor when rightclick
     @createInvisibleTextarea event
+    @setKeyView()
     event

   createInvisibleTextarea:(eventData)->
@@ -178,6 +180,7 @@

     # remove on any of these events
     @textarea.on 'copy cut paste', (event)=>
+      @setKeyView()
       @utils.wait 1000, => @textarea.remove()
       yes


[fix] Corrected parsing form attributes for array values (5m)

--- vendor/assets/javascripts/neat/model_editor.coffee
+++ vendor/assets/javascripts/neat/model_editor.coffee
@@ -116,7 +116,7 @@
       # person:
       #   address:
       #     zip: 63303
-      parts = _.without(name.split(/\[([^\]]+)\]/), '')
+      parts = _.without(name.split(/\[([^\]]*)\]/), '')
       name = parts.pop()
       isArray = false
       while name is '[]'


Removed debugging statement

Removed debugging statement

--- game.coffee
+++ game.coffee
@@ -35,7 +35,6 @@
       p.currentLine() for p in @players

     timer_tick: ->
-      console.log "Tick!"
       new_time = SynchronizedTime.getTime()
       elapsed_time = new_time - @old_time


Cleaning

Cleaning

--- test/tests/LayerEventsTest.coffee
+++ test/tests/LayerEventsTest.coffee
@@ -196,16 +196,4 @@
 			handler = -> console.log "hello"
 			layerA.on(Events.Pinch, handler)
 			layerA.removeAllListeners(Events.Pinch)
-			layerA.listeners(Events.Pinch).length.should.equal 0
-
-		# it "should work with event helpers", (done) ->
-
-		# 	layer = new Layer
-
-		# 	layer.onMouseOver (event, aLayer) ->
-		# 		aLayer.should.equal(layer)
-		# 		@should.equal(layer)
-		# 		done()
-
-		# 	simulate.mouseover(layer._element)
-
+			layerA.listeners(Events.Pinch).length.should.equal 0


fix input composition events firing too often on safari

--- resources/assets/coffee/react/beatmaps/search-panel.coffee
+++ resources/assets/coffee/react/beatmaps/search-panel.coffee
@@ -112,8 +112,11 @@

   onChange: (event) =>
     query = event.target.value
-    @pinnedInputRef.current.value = query
-    @inputRef.current.value = query
+    if @pinnedInputRef.current.value != query
+      @pinnedInputRef.current.value = query
+
+    if @inputRef.current.value != query
+      @inputRef.current.value = query

     controller.updateFilters { query }


allow multiselect

allow multiselect

--- resources/assets/coffee/react/beatmaps/search-panel.coffee
+++ resources/assets/coffee/react/beatmaps/search-panel.coffee
@@ -87,6 +87,7 @@
         title: osu.trans('beatmaps.listing.search.filters.general')
         options: filters.general
         default: @props.filterDefaults.general
+        multiselect: true
         selected: @props.filters.general

       el Beatmaps.SearchFilter,


Extend message on loaded models and fix undefined ke problem

--- assets/scripts/lib/travis/model.coffee
+++ assets/scripts/lib/travis/model.coffee
@@ -36,7 +36,14 @@
   ).property('incomplete', 'isLoaded')

   loadTheRest: (key) ->
-    console.log 'Load missing fields for', @constructor, "because of missing key '#{key}'"
+    # for some weird reason key comes changed to a string and for some weird reason it even is called with
+    # undefined key
+    return if !key || key == 'undefined'
+
+    message = "Load missing fields for #{@constructor.toString()} because of missing key '#{key}', cid: #{@get('clientId')}"
+    if @constructor.isAttribute('state') && key != 'state'
+      message += ", in state: #{@get('state')}"
+    console.log message
     return if @get('isCompleting')
     @set 'isCompleting', true


Applying style queries through neft:fragment/neft:each bug fixed

--- lib/tasks/build/parse/documents.coffee
+++ lib/tasks/build/parse/documents.coffee
@@ -31,9 +31,14 @@
 	Document.onError onErrorListener = (name) ->
 		parseFile name, "./views/#{name}.html"

+	Document.onBeforeParse onBeforeParseListener = (file) ->
+		if platform isnt 'node'
+			Document.Style.applyStyleQueriesInDocument file
+		return
+
 	Document.onParse onParseListener = (file) ->
 		if platform isnt 'node'
-			Document.Style.extendDocumentByStyles file
+			Document.Style.createStylesInDocument file
 		return

 	parseFile = (name, path) ->
@@ -64,6 +69,7 @@
 				path: "#{OUT_DIR}/#{name}.json"

 		Document.onError.disconnect onErrorListener
+		Document.onBeforeParse.disconnect onBeforeParseListener
 		Document.onParse.disconnect onParseListener

 		log.end logtime


Concatenate pending request message to remove excess whitespace

--- src/coffee/cilantro/setup.coffee
+++ src/coffee/cilantro/setup.coffee
@@ -6,5 +6,5 @@
     # This ensures data is not silently lost
     $(window).on 'beforeunload', ->
        if $.hasPendingRequest()
-           return "Wow, you're quick! Your data is being saved.
-               It will only take a moment."
+           return "Wow, you're quick! Your data is being saved. " +
+                "It will only take a moment."


lookup_or_generate

--- test/lib/user.iced
+++ test/lib/user.iced
@@ -421,6 +421,11 @@

   lookup : (u) -> @_lookup[u]

+  lookup_or_gen : (u) ->
+    unless (ret = @_lookup[u])?
+      @_lookup[u] = ret = User.generate u
+    return ret
+
   get : (i) -> @_list[i]

   cleanup : (cb) ->


log all errors in getProjectDocs

log all errors in getProjectDocs

--- services/document-updater/app/coffee/ProjectManager.coffee
+++ services/document-updater/app/coffee/ProjectManager.coffee
@@ -65,12 +65,16 @@
 			_callback(args...)

 		RedisManager.checkOrSetProjectState project_id, projectStateHash, (error, projectStateChanged) ->
-			return callback(error) if error?
+			if error?
+				logger.error err: error, project_id: project_id, "error getting/setting project state in getProjectDocs"
+				return callback(error)
 			# we can't return docs if project structure has changed
 			return callback Errors.ProjectStateChangedError("project state changed") if projectStateChanged
 			# project structure hasn't changed, return doc content from redis
 			RedisManager.getDocIdsInProject project_id, (error, doc_ids) ->
-				return callback(error) if error?
+				if error?
+					logger.error err: error, project_id: project_id, "error getting doc ids in getProjectDocs"
+					return callback(error)
 				jobs = []
 				docs = []
 				for doc_id in doc_ids or []
@@ -79,18 +83,21 @@
 							# check the doc version first
 							RedisManager.getDocVersion doc_id, (error, version) ->
 								if error?
-									logger.error err: error, project_id: project_id, doc_id: doc_id, "error getting project doc version"
+									logger.error err: error, project_id: project_id, doc_id: doc_id, "error getting project doc version in getProjectDocs"
 									return cb(error)
 								# skip getting the doc if we already have that version
-								return cb() if version? and version is excludeVersions[doc_id]
+								if version? and version is excludeVersions[doc_id]
+									logger.error err: error, project_id: project_id, doc_id: doc_id, version: version, "skipping doc version in getProjectDocs"
+									return cb()
 								# otherwise get the doc lines from redis
 								RedisManager.getDocLines doc_id, (error, lines) ->
 									if error?
-										logger.error err: error, project_id: project_id, doc_id: doc_id, "error getting project doc lines"
+										logger.error err: error, project_id: project_id, doc_id: doc_id, "error getting project doc lines in getProjectDocs"
 										return cb(error)
 									try
 										docs.push {_id: doc_id, lines: JSON.parse(lines), v: version}
 									catch e
+										logger.error err: e, project_id: project_id, doc_id: doc_id, lines: lines, version: version, "error parsing doc lines in getProjectDocs"
 										return cb(e)
 									cb()
 				async.series jobs, (error) ->


Fix conda and apt-commands

Fix conda and apt-commands

--- build-index.coffee
+++ build-index.coffee
@@ -17,7 +17,7 @@

 	getCondaPackages: () ->
 		result = {}
-		conda_output = child_process.execSync 'conda search ".*" --names-only '
+		conda_output = child_process.execSync 'sudo conda search ".*" --names-only '
 		names = _.without(conda_output.toString().split('\n').slice(1), '')  # skip first line of output
 		(result[name] = {
 			name: name.trim(),
@@ -96,7 +96,7 @@
 			callback(null, python_packages)

 	getAptCranPackages: () ->
-		child_process.execSync 'sudo apt-get update '
+		child_process.execSync 'apt-get update '
 		apt_packages = child_process.execSync ' apt-cache search "r-cran-.*" | grep "^r-cran.*$"'
 		lines = apt_packages.toString().split('\n')
 		packages = lines.map (line) ->


fixed an assert

fixed an assert

--- src/vm.coffee
+++ src/vm.coffee
@@ -84,7 +84,7 @@
             env: for x in env then x[..]
             stack: stack[...frame[3]]
             pc: frame[1].addr + frame[1].size - 1
-          assert code[contPc] is RET
+          assert code[cont.pc] is RET
           stack.push (r) -> {code, env, stack, pc} = cont; stack.push r
       else aplError 'Unrecognized instruction: ' + code[pc - 1] + ', pc:' + pc
   return


ComputeController::fetchRewards helper to get reward amount as GB

--- client/Main/providers/computecontroller.coffee
+++ client/Main/providers/computecontroller.coffee
@@ -676,8 +676,27 @@
     provider ?= "koding"

     @fetchUserPlan (plan)=> @fetchPlans (plans)=>
-      @fetchUsage { provider }, (err, usage)->
-        callback err, { plan, plans, usage }
+      @fetchUsage { provider }, (err, usage)=>
+        @fetchRewards { unit: 'GB' }, (err, reward)->
+          callback err, { plan, plans, usage, reward }
+
+  fetchRewards: (options, callback)->
+
+    {unit} = options
+
+    options =
+      unit  : 'MB'
+      type  : 'disk'
+
+    KD.remote.api.JReward.fetchEarnedAmount options, (err, amount)->
+
+      if err
+        amount = 0
+        warn err
+
+      amount = Math.floor amount / 1000  if unit is 'GB'
+
+      callback null, amount

   findMachineFromMachineId: (machineId)->


compute/connect: rename `require` option to `storage`

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

--- client/app/lib/providers/connectcompute.coffee
+++ client/app/lib/providers/connectcompute.coffee
@@ -8,14 +8,14 @@

 makeState = (config, props) ->

-  unless config.require
+  unless config.storage
     console.warning \
-      'You need to specify the requirements via `require` config option.'
+      'You need to specify the requirements via `storage` config option.'
     return {}

   { storage } = kd.singletons.computeController

-  state = config.require.reduce (acc, pluralName) ->
+  state = config.storage.reduce (acc, pluralName) ->
     singularName = makeSingular pluralName
     # if we pass stackId, machineId or templateId our wrapped component will
     # receive a prop named stack, machine, template.


video: model improve getChannel method

video: model improve getChannel method

--- client/app/lib/videocollaboration/model.coffee
+++ client/app/lib/videocollaboration/model.coffee
@@ -43,12 +43,14 @@
    * @return {KDView} view
   ###
   getView: -> @view
+
+
+  ###*
+   * Returns the channel.
+   *
+   * @return {SocialChannel} channel
+  ###
   getChannel: -> @channel
-
-
-  ###*
-   *
-  ###


No need a default section

No need a default section

--- client/landing/app/MainApp/kodingrouter.coffee
+++ client/landing/app/MainApp/kodingrouter.coffee
@@ -46,8 +46,8 @@

       if KD.isLoggedIn()
         @handleRoute @userRoute or @getDefaultRoute(), replaceState: yes
-      else
-        KD.getSingleton('mainController').doLogin()
+      # else
+      #   KD.getSingleton('mainController').doHome()

   cleanupRoute:(contentDisplay)->
     delete @openRoutes[@openRoutesById[contentDisplay.id]]


Add new LocalStorage and LocalStorageController still WIP

--- client/landing/app/MainApp/localstorage.coffee
+++ client/landing/app/MainApp/localstorage.coffee
@@ -1,77 +1,51 @@
+class LocalStorage extends AppStorage
+
+  fetchStorage:(callback=noop)->
+    callback window["localStorage"][@getSignature()]
+    KD.utils.defer => @emit "ready"
+
+  getSignature:(key, group)->
+    "koding-#{@_applicationID}-#{@_applicationVersion}-#{group}-#{key}"
+
+  setValue: (key, value, callback, group = 'bucket')->
+    sign = "#{group}.#{key}"
+    @_storageData[sign] = value
+    window["localStorage"][@getSignature key, group] = JSON.stringify value
+    KD.utils.defer => callback? null
+
+  getValue: (key, group = 'bucket')->
+    data = @_storageData["#{group}.#{key}"]
+    return data  if data
+
+    data = window["localStorage"][@getSignature key, group]
+    if data
+      try
+        data = JSON.parse data
+      catch e
+        warn "parse failed", e
+    return data
+
+  fetchValue:(key, callback, group = 'bucket')->
+    callback? @getValue key, group
+
+  unsetKey: (key, callback, group = 'bucket')->
+    delete window["localStorage"][@getSignature key, group]
+    delete @_storageData["#{group}.#{key}"]
+
 class LocalStorageController extends KDController

-  idPrefix = 'koding-'
+  constructor:->
+    super
+    @localStorages = {}

-  constructor:(options,data)->
-    super options,data
+  storage:(appName, version)->

-    @storage = window["localStorage"]
+    if @localStorages[appName]?
+      storage = @localStorages[appName]
+    else
+      storage = @localStorages[appName] = new LocalStorage appName, version

-    # this even will only fire when a different window changes the LS
-    window.addEventListener 'storage', @storageEvent ,false
+    return storage

-  storageEvent:(event)->
-    log 'storage event found.', event
-
-  assureStorageIndex:->
-  createStorageIndex:->
-  deleteStorageIndex:->
-
-  # DELETER for key/value or set value to null
-  deleteValueByKey:(key)->
-    @setValueByKey key, null
-
-  deleteValueById:(id)->
-    @setValueByKey @getKeyFromId, null
-
-  deleteKey:(key)->
-    @storage.removeItem key
-
-  deleteId:(idKey)->
-    @deleteKey @getIdFromKey idKey
-
-  # GET values from id or key
-  getKeyFromId:(id)->
-    id.replace idPrefix, ''
-
-  getIdFromKey:(key)->
-    @getSlug key
-
-  getValueByIndex:(index)->
-    if @storage.length > index and @storage.key index
-      @storage.key index
-    else null
-
-  getValueByKey:(key)->
-    @storage.getItem key
-
-  getValueById:(id)->
-    @getValueByKey @getSlug(id)
-
-  # SETTER
-  setValueByKey:(key, value)->
-    @storage.setItem key,value
-
-  setValueById:(id, value)->
-    @setValueByKey @getSlug(id), value
-
-
-  # SIZE related methods
-  getStorageLength:->
-    @storage.length
-
-  getStorageSize:->
-  getStorageRemainingSize:->
-  getStorageUsedSize:->
-    JSON.stringify(@storage).length # in bytes
-
-
-  hasKey:(key)->
-    @storage.getItem(key) isnt null
-
-  hasId:(id)->
-    @hasKey @getSlug id
-
-
-  getSlug:(id)-> idPrefix+id
-
+# Let people can use AppStorage
+KD.classes.LocalStorage = LocalStorage


Update pt.coffe

Update pt.coffe

Resend verification e-mail translation

--- t9n/pt.coffee
+++ t9n/pt.coffee
@@ -104,7 +104,9 @@
       "Verify email link expired": "O link de verificação de e-mail expirou"
       "Verify email link is for unknown address": "O link de verificação de e-mail está configurado para um endereço desconhecido"
       "Verify email link expired":"E-mail de verificação recebido?"
-      "Send again": "Renviar"
+      "Send again": "Renviar",
+      "Send the verification email again" : "Reenviar e-mail de verificação",
+      "Send email again" : "Reenviar e-mail"

     #---- match
       "Match failed": "Senhas não coincidem"


flux/teams: sort teammembers by first, last name and email

--- client/app/lib/flux/teams/getters.coffee
+++ client/app/lib/flux/teams/getters.coffee
@@ -1,6 +1,7 @@
 toImmutable = require 'app/util/toImmutable'
 immutable   = require 'immutable'
 isEmailValid = require 'app/util/isEmailValid'
+getFullnameFromAccount = require 'app/util/getFullnameFromAccount'

 team = ['TeamStore']
@@ -33,13 +34,13 @@
   re = new RegExp(value, 'i')

   if member.get('status') is 'pending'
-    re.test(member.get('email')) or \
-    re.test(member.get('firstname')) or \
-    re.test(member.get('lastname'))
+    re.test(member.get('firstName')) or \
+    re.test(member.get('lastName')) or \
+    re.test(member.get('email'))
   else
-    re.test(member.get('profile').get('email')) or \
-    re.test(member.get('profile').get('firstname')) or \
-    re.test(member.get('profile').get('lastname'))
+    re.test(member.get('profile').get('firstName')) or \
+    re.test(member.get('profile').get('lastName')) or \
+    re.test(member.get('profile').get('email'))

 membersWithPendingInvitations = [
@@ -53,9 +54,25 @@
     return members
 ]

+sortedMembersWithPendingInvitations = [
+  membersWithPendingInvitations
+  (members) ->
+
+    members.sortBy (member) ->
+      if member
+        if member.get('status') is 'pending'
+          member.get('firstName')  if member.get 'firstname'
+          member.get('lastName')  if member.get 'lastName'
+          member.get('email')
+        else
+          member.getIn(['profile', 'firstName'])  if member.getIn(['profile', 'firstName'])
+          member.getIn(['profile', 'lastName'])  if member.getIn(['profile', 'lastName'])
+          member.getIn(['profile', 'email'])
+]
+

 filteredMembersWithRole = [
-  membersWithPendingInvitations
+  sortedMembersWithPendingInvitations
   searchInputValue
   (members, value) ->
     return members  if value is ''


teams/getter: add getter to get logged in user email

--- client/app/lib/flux/teams/getters.coffee
+++ client/app/lib/flux/teams/getters.coffee
@@ -134,6 +134,7 @@

 module.exports = {
   team
+  loggedInUserEmail
   membersWithRole
   TeamMembersIdStore
   invitationInputValues


set @_unsaved in constructor

set @_unsaved in constructor

--- client/lanes/models/ChangeMonitor.coffee
+++ client/lanes/models/ChangeMonitor.coffee
@@ -6,20 +6,20 @@
 class Lanes.Models.ChangeMonitor
     constructor: (model) ->
         model.on('change', this.onChange, this)
+        @_unsaved = {}

     onChange: (record, options) ->
         attrs = record.changedAttributes()
         return if _.isEmpty(attrs)
         if attrs.isDirty == false
-            delete @_unsaved
+            @_unsaved = {}
         else
-            @_unsaved ||= {}
             this.recordChanges(record, _.keys(attrs))

     recordChanges: (record, names) ->
         # console.log "Change: #{names}"
-        #console.log record.getAttributes(props:true, session: true)
+        # console.log record.getAttributes(props:true, session: true)
         for name in names
             if name != record.idAttribute && record._definition[name] && !record._definition[name].session
                 record.isDirty = true


Remove unneeded description from query

Remove unneeded description from query

--- components/block_v2/queries/block.coffee
+++ components/block_v2/queries/block.coffee
@@ -2,7 +2,6 @@
   fragment blockThumb on Connectable {
     id
     title
-    description
     klass
     created_at(relative: true)
     updated_at(relative: true)


temp fix for couldn't restore your session warning

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -217,7 +217,10 @@
     constructor = @
     JSession.one {clientId}, (err, session)->
       return callback err  if err
-      if not session? or session.username isnt username
+      # temp fix:
+      # this broke login, reverted. - SY
+      # if not session? or session.username isnt username
+      unless session
         return callback createKodingError 'Could not restore your session!'

       bruteForceControlData =


SocialWorker: check if account exists

SocialWorker: check if account exists

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -1950,9 +1950,11 @@
   fetchHomepageView: (options, callback) ->

     { account, bongoModels } = options
+
     @fetchAccount 'koding', (err, account) ->
-      if err then callback err
-      else account.fetchHomepageView options, callback
+      return callback err  if err
+      return callback new KodingError 'Account not found'  unless account
+      account.fetchHomepageView options, callback

   confirmEmail: (callback) ->


Wait to measure table size, and set min

Wait to measure table size, and set min

--- src/components/scores/index.cjsx
+++ src/components/scores/index.cjsx
@@ -65,17 +65,19 @@
     @sizeTable()

   sizeTable: ->
-    @setState({tableWidth: @tableWidth(), tableHeight: @tableHeight()})
+    _.delay( =>
+      @setState({tableWidth: @tableWidth(), tableHeight: @tableHeight()}) if @isMounted()
+    , 100)

   tableWidth: ->
     table = React.findDOMNode(@refs.tableContainer)
-    table.clientWidth
+    Math.max(400, table.clientWidth)

   tableHeight: ->
     windowEl = @_getWindowSize()
     table = React.findDOMNode(@refs.tableContainer)
     bottomMargin = 40
-    windowEl.height - table.offsetTop - bottomMargin
+    Math.max(500, windowEl.height - table.offsetTop - bottomMargin)

   renderHeadingCell: (heading, i) ->
     i += FIRST_DATA_COLUMN # for the first/last name colums


Correctly form POST request body

Correctly form POST request body

Everything needs to be wrapped in an object within the `data` key

--- src/kissmetrics-batch.coffee
+++ src/kissmetrics-batch.coffee
@@ -99,7 +99,7 @@
         'X-KM-ApiKey': apiKey
         'Connection': 'close'

-    request.end JSON.stringify(queue.get())
+    request.end JSON.stringify({data: queue.get()})
     queue.done()
     request


chat: compatibility with Webkit by looking datetime attribute object instead of relying on HTMLTimeElement interface.

According to https://developer.mozilla.org/fr/docs/Web/API/HTMLTimeElement
HTMLTimeElement is currently not supported by Safari/Webkit and is available since
version 62 of Chromium/Blink.

--- app/assets/javascripts/chat.coffee
+++ app/assets/javascripts/chat.coffee
@@ -99,8 +99,9 @@
         $(@).replaceWith html+orig.slice(idx) if html

   norlogize_left: (x) ->
-    date = /\d{4}-\d{2}-\d{2}/.exec(x.dateTime)
-    time = /\d{2}:\d{2}:\d{2}/.exec(x.dateTime)
+    norlogeDatetime = $(x).attr("datetime")
+    date = /\d{4}-\d{2}-\d{2}/.exec(norlogeDatetime)
+    time = /\d{2}:\d{2}:\d{2}/.exec(norlogeDatetime)
     index = @board.find(".board-left time[data-clock-date=\"" + date + "\"][data-clock-time=\"" + time + "\"]").length + 1
     x.dataset.clockDate = date
     x.dataset.clockTime = time


Bulk of recipient API endpoint

Bulk of recipient API endpoint

--- app/api/recipient.coffee
+++ app/api/recipient.coffee
@@ -6,3 +6,27 @@
 #
 # Copyright  2015 Tranzit Development Team
 angular.module 'Tranzit.api.recipient', ['Tranzit.config']
+
+# Authentication API service
+.service 'TranzitRecipient', ($http, $localStorage, $q, ApiConfig, TranzitAuthSession) ->
+
+  # Keep a reference of @ in case we need it later in nested functions
+  self = @
+
+  @getRecipient = (recipientID) ->
+    url = ':host/api/recipients' # TODO: check on this
+
+    # HTTP call details here
+    config =
+      method:   'GET'
+      url:      _.template(url)(host: ApiConfig.host)
+      data:     JSON.stringify _.extend({}, recipientID) # TODO
+      headers:
+        'Content-Type': 'application/json'
+        'X-Tranzit-Auth': TranzitAuthSession.user?.token
+
+    # Send request and generate a promise
+    return $http(config).then (data) ->
+      return data.data.result
+
+  return @


Update Create Room with new protocol

Update Create Room with new protocol

--- src/create-room.coffee
+++ src/create-room.coffee
@@ -89,22 +89,22 @@
               .then (body) ->
                 # TODO: replace this with robot.emit 'slack.attachment'
                 if robot.adapterName is 'slack'
-                  att =
-                    fallback: "Major incident <#{msgObj.id}> - #{msgObj.title}"
-                    color: "danger",
-                    title: "Major incident <#{msgObj.id}> - #{msgObj.title}"
-                    title_link: docengine_url
-                    text: msgObj.description
-                    fields: [
-                      {
-                        title: "Status",
-                        value: msgObj.Status
-                        short: true
-                      }
-                    ]
-                  data =
-                    text: "Major incident <#{msgObj.id}>",
-                    attachments:[att]
+                  # att =
+                  #   fallback: "Major incident <#{msgObj.id}> - #{msgObj.title}"
+                  #   color: "danger",
+                  #   title: "Major incident <#{msgObj.id}> - #{msgObj.title}"
+                  #   title_link: docengine_url
+                  #   text: msgObj.description
+                  #   fields: [
+                  #     {
+                  #       title: "Status",
+                  #       value: msgObj.Status
+                  #       short: true
+                  #     }
+                  #   ]
+                  data =
+                    text: msgObj.description,
+                    attachments:[msgObj.attachments]

                   # robot.emit 'slack.attachment', data
                   robot.sm_ext.postMessage channelId, data


Method is just unmaskInvalidField (singular)

--- client/lanes/react/mixins/FieldErrors.coffee
+++ client/lanes/react/mixins/FieldErrors.coffee
@@ -20,5 +20,5 @@
         )

     onFieldInteraction: ->
-        @getInvalidModel().unmaskInvalidFields( @getInvalidFieldName() )
+        @getInvalidModel().unmaskInvalidField( @getInvalidFieldName() )
 }


Fixed Hubo2+ fingers for hubo-ach (Eric Rock edition). TODO: Update trajectories.

--- src/documents/src/hubo.js.coffee
+++ src/documents/src/hubo.js.coffee
@@ -88,8 +88,8 @@
     # Add finger motor
     motor = {}
     motor.name = name
-    motor.lower_limit = 0
-    motor.upper_limit = 1.4
+    motor.lower_limit = -1
+    motor.upper_limit = 1
     # Note: we are VERY MUCH expecting a TLA for 'name'
     hand = if (name[0]=='L') then 'left' else 'right'
     fingers = ['Thumb', 'Index', 'Middle', 'Ring', 'Pinky']
@@ -100,7 +100,8 @@
         get: -> return @_value
         set: (val) ->
           val = clamp(val,this)
-          @_value = val
+          @_value = val
+          val = (-val + 1)/2*1.4
           _robot.joints[@full_name + 'Knuckle1'].value = val
           _robot.joints[@full_name + 'Knuckle2'].value = val
           _robot.joints[@full_name + 'Knuckle3'].value = val


add infoWindow with the aircraft code back

--- public/angular/directives.coffee
+++ public/angular/directives.coffee
@@ -87,6 +87,11 @@
 							position: 'absolute'
 							transition: 'transform 2s'
 						)
+						aircraft.infoWindow = new google.maps.InfoWindow
+						  content: code
+						aircraft.icon.on 'click', =>
+							$log.info "click fired"
+							aircraft.infoWindow.open @getMap()

 					if aircraft.shouldTween
 						aircraft.tween?.stop()
@@ -105,7 +110,7 @@
 					aircraft
 				onAdd: ->
 					panes = @getPanes()
-					overlayLayer = panes.overlayLayer
+					overlayLayer = panes.overlayMouseTarget
 					@parentDiv.appendTo overlayLayer
 				onRemove: ->
 					@parentDiv.remove()
@@ -125,6 +130,7 @@
 						top: (posPixel.y - aircraft.icon.height()/2) + 'px'
 						left: (posPixel.x - aircraft.icon.width()/2) + 'px'
 						transform: "rotate(#{heading}rad)"
+					aircraft.infoWindow.setPosition posLatLng
 					null

 			overlay = new AircraftOverlay


unpends test

unpends test

--- components/artwork_filter/test/filter.coffee
+++ components/artwork_filter/test/filter.coffee
@@ -56,10 +56,10 @@
   describe '#priced', ->
     describe 'already fetched', ->
       beforeEach ->
-        @priced = @filter.toggle 'for-sale', true
+        @priced = @filter.toggle 'for_sale', true
         @filter.by 'foo', 'bar'

-      xit 'returns the already fetched priced filter state without fetching again', ->
+      it 'returns the already fetched priced filter state without fetching again', ->
         Backbone.sync.callCount.should.equal 2
         @filter.priced().should.equal @priced
         Backbone.sync.callCount.should.equal 2


Add shebang to main.coffee.

Add shebang to main.coffee.

--- main.coffee
+++ main.coffee
@@ -1,3 +1,5 @@
+#!/usr/bin/env coffee
+
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as published by
 # the Free Software Foundation; either version 3 of the License, or


Add histo class.

Add histo class.

--- main.coffee
+++ main.coffee
@@ -193,6 +193,8 @@
         chartArea = @obj
         chartArea.attr("width", width + margin.left + margin.right)
         chartArea.attr("height", height + margin.top + margin.bottom)
+        chartArea.attr('class', 'histo')
+

         @plotArea = chartArea.append("g")
             .attr("transform", "translate(#{margin.left},#{margin.top})")


Increase volume range for oscillators

Increase volume range for oscillators


socialcontroller: bypass isFromOtherBrowser check when user is not the post owner

--- client/Main/socialapicontroller.coffee
+++ client/Main/socialapicontroller.coffee
@@ -229,12 +229,15 @@
     return no  if KD.isTesting

     {message} = message  unless message.typeConstant?
+
     {_inScreenMap}  = KD.singletons.socialapi

-    {id, clientRequestId} = message
-
-    # id is needed for join/leave activity messages
-    inside = _inScreenMap[id] or _inScreenMap[clientRequestId]
+    # when I am not the message owner, it is obviously from another browser
+    return yes  unless message.accountId is KD.whoami().socialApiId
+
+    {clientRequestId} = message
+
+    inside = _inScreenMap[clientRequestId]

     return not inside

@@ -304,7 +307,7 @@
     {clientRequestId} = options
     {_inScreenMap} = KD.singletons.socialapi

-    _inScreenMap[clientRequestId] = yes
+    _inScreenMap[clientRequestId] = yes  if clientRequestId

   addToScreenMap : addToScreenMap
@@ -522,7 +525,7 @@
       err = {message: "An error occurred"}

       endPoint = "/api/social/channel/#{options.id}/history?#{serialize(options)}"
-      KD.utils.doXhrRequest {type: 'GET', endPoint, async: no}, (err, response) ->
+      KD.utils.doXhrRequest {type: 'GET', endPoint, async: yes}, (err, response) ->
         return callback err  if err

         return callback null, mapActivities response


kontrol.coffee: fix getKites and implement watchKites

--- client/app/MainApp/kite/kontrol.coffee
+++ client/app/MainApp/kite/kontrol.coffee
@@ -18,8 +18,9 @@
     @kite = new NewKite kite, authentication
     @kite.connect()

-  # Calls the callback function with the list of NewKite instances.
-  # The returned kites are not connected. You must connect with NewKite.connect().
+  # getKites Calls the callback function with the list of NewKite instances.
+  # The returned kites are not connected. You must connect with
+  # NewKite.connect().
   #
   # Query parameters are below from general to specific:
   #
@@ -31,29 +32,58 @@
   #   hostname    string
   #   id          string
   #
-  getKites: (query={}, onKites, onError, onEvent)->
-    if not query.username
-      query.username = "#{KD.nick()}"
-    if not query.environment
-      query.environment = "production"
+  getKites: (query={}, callback = noop)->
+    query = @_sanitizeQuery query

-    eventCB = (e)=>
+    @kite.tell "getKites", [query], (res)=>
+      err = res.withArgs[0]
+      kites = res.withArgs[1]
+      return callback err, null  if err
+
+      kiteInstances = []
+      for k in kites
+        kiteInstances.push @_createKite k
+
+      callback null, kiteInstances
+
+
+  # watchKites watches for Kites that matches the query. The onEvent functions
+  # is called for current kites and every new kite event.
+  watchKites: (query={}, callback)->
+    return warn "callback is not defined "  unless callback
+
+    query = @_sanitizeQuery query
+
+    onEvent = (e)=>
       log "kite event: ", e.action, {e}
-      onEvent e.action, @_createKite {kite: e.kite, token: e.token}
+      callback e.action, @_createKite {kite: e.kite, token: e.token}

-    if onEvent
-      args = [query, eventCB]
-    else
-      args = [query]
+    args = [query, onEvent]

-    @kite.tell "getKites", args, (err, kites)=>
-      log "getKites result: ", {err}, {kites}
-      if err
-        onError err
-      else
-        onKites (@_createKite k for k in kites)
+    @kite.tell "getKites", args, (res)=>
+      err = res.withArgs[0]
+      kites = res.withArgs[1]
+      return callback err, null  if err
+
+      for k in kites
+        e =
+          action : @KiteAction.Register
+          kite   : k
+          token  : k.authentication.key
+
+        callback e.action, @_createKite {kite: e.kite, token: e.token}

   # Returns a new NewKite instance from Kite data structure coming from
   # getKites() and watchKites() methods.
   _createKite: (k)->
     return new NewKite k.kite, {type: "token", key: k.token}
+
+  _sanitizeQuery: (query) ->
+    query.username    = "#{KD.nick()}"  unless query.username
+    query.environment = "production"    unless query.environment
+
+    return query
+
+  KiteAction :
+    Register   : "REGISTER"
+    Deregister : "DEREGISTER"


[Email Confirmation] Removed location replace and used KD.showError instead of KDNotificationView

--- client/app/MainApp/kodingrouter.coffee
+++ client/app/MainApp/kodingrouter.coffee
@@ -309,16 +309,13 @@
       '/:name?/Verify/:confirmationToken': ({params:{confirmationToken}})->
         confirmationToken = decodeURIComponent confirmationToken
         KD.remote.api.JEmailConfirmation.confirmByToken confirmationToken, (err)=>
-          location.replace '#'
           if err
             error err
-            new KDNotificationView
-              title: err.message
-              duration : 3000
+            KD.showError err
           else
             new KDNotificationView
               title: "Thanks for confirming your email address!"
-          @clear()
+            @clear()

       '/member/:username': ({params:{username}})->
         @handleRoute "/#{username}", replaceState: yes


Check if related function is there

Check if related function is there

--- client/app/MainApp/kodingrouter.coffee
+++ client/app/MainApp/kodingrouter.coffee
@@ -229,7 +229,7 @@

     animateToForm = (formName, force = no) ->
       { mainTabView } = KD.getSingleton 'mainView'
-      appsAreOpen = mainTabView.getVisibleHandles().length > 0
+      appsAreOpen = mainTabView.getVisibleHandles?().length > 0
       if not force and formName in ['login', 'register'] and appsAreOpen
         ok = no
         modal = KDModalView.confirm


Client: Fix preloading of sprites

Client: Fix preloading of sprites

--- client/app/js/views/battle_view.coffee
+++ client/app/js/views/battle_view.coffee
@@ -1093,10 +1093,11 @@
         formes = gen.FormeData[name]
         formeNames = _.keys(formes)
         formeNames = _.filter formeNames, (formeName) ->
-          forme == formeName || formes[forme].isBattleOnly
+          forme == formeName || formes[formeName].isBattleOnly
         for formeName in formeNames
           PokemonSprite(id, formeName, front: front, shiny: shiny)
-    pokemonUrls = _.flatten(teams)
+    # First pokemon of each team is loaded first, then second, etc.
+    pokemonUrls = _.flatten(_.zip(teams...))
     for pokemonUrl in pokemonUrls
       image = new Image()
       image.src = pokemonUrl


Kloud: info: return state as if for NotInitialized machines

--- client/app/lib/kite/kites/kloud.coffee
+++ client/app/lib/kite/kites/kloud.coffee
@@ -112,21 +112,25 @@
     { kontrol, computeController } = kd.singletons
     { klient } = kontrol.kites
     machine    = computeController.findMachineFromMachineId machineId
+    deferredCallback = (res) -> kd.utils.defer -> callback res

     if not machine or not machineId
-      return callback null
+      return deferredCallback null

     managed    = machine.provider is 'managed'
     klientKite = klient?[machine.uid]

-    if machine.status.state is Machine.State.Running or managed
+    if machine.status.state is Machine.State.NotInitialized
+      return deferredCallback { State: Machine.State.NotInitialized, via: 'klient' }
+
+    else if machine.status.state is Machine.State.Running or managed
       unless klientKite?
         klientKite = kontrol.getKite
           name            : 'klient'
           queryString     : machine.queryString
           correlationName : machine.uid
     else
-      return callback null
+      return deferredCallback null

     klientKite.ping()


FIX: Ensure task resource URLs update on task change

--- src/app/projects/states/dashboard/directives/task-dashboard/directives/task-description-card/task-description-card.coffee
+++ src/app/projects/states/dashboard/directives/task-dashboard/directives/task-description-card/task-description-card.coffee
@@ -8,10 +8,16 @@
   scope:
     task: '='
   controller: ($scope, Task, listenerService, gradeService) ->
-    # Resource download URLs
-    $scope.urls =
-      taskSheet: Task.getTaskPDFUrl($scope.task.unit(), $scope.task.definition)
-      resources: Task.getTaskResourcesUrl($scope.task.unit(), $scope.task.definition)
+    # Cleanup
+    listeners = listenerService.listenTo($scope)
+    # Required changes when task changes
+    listeners.push $scope.$watch('task.id', ->
+      return unless task?
+      # Resource download URLs
+      $scope.urls =
+        taskSheet: Task.getTaskPDFUrl($scope.task.unit(), $scope.task.definition)
+        resources: Task.getTaskResourcesUrl($scope.task.unit(), $scope.task.definition)
+    )
     # Analytics event for when task resource is downloaded
     $scope.downloadEvent = (type) ->
       analyticsService.event 'Task Sheet', "Downloaded Task #{type}"


client: utilities: put intercom above chatlio

--- client/home/lib/utilities/index.coffee
+++ client/home/lib/utilities/index.coffee
@@ -40,5 +40,5 @@

     if canEdit
         @wrapper.addSubView headerize  'Integrations'
+        @wrapper.addSubView sectionize 'Intercom Integration', HomeUtilitiesIntercomIntegration
         @wrapper.addSubView sectionize 'Customer Feedback', HomeUtilitiesCustomerFeedback
-        @wrapper.addSubView sectionize 'Intercom Integration', HomeUtilitiesIntercomIntegration


Expiry date formatter should parse year between 0 and 99.

--- src/expiry_date_formatter.coffee
+++ src/expiry_date_formatter.coffee
@@ -16,7 +16,7 @@
     return '' unless value

     { month, year } = value
-    year = year % 10
+    year = year % 100

     super zpad2(month) + zpad2(year)


Add credentials get/post routes

Add credentials get/post routes

--- routes/users/index.coffee
+++ routes/users/index.coffee
@@ -1,5 +1,6 @@
 _ = require 'underscore'
 UsersController = require "#{__dirname}/../../controllers/users"
+UserCredentialsController = require "#{__dirname}/../../controllers/user_credentials"
 User = require "#{__dirname}/../../models/user"

 handler = (app)->
@@ -33,5 +34,18 @@
       else
         res.send 200

+  app.get '/users/:id/credentials', (req, res)->
+    UserCredentialsController.getCredentials req.params.id, (err, credentials) ->
+      if err
+        res.send 404, error: "No credentials set for user #{req.parmas.id}"
+      else
+        res.send credentials
+
+  app.post '/users/:id/credentials', (req, res)->
+    UserCredentialsController.makeCredentials req.params.id, (err, credentials)->
+      if err
+        res.send 404, error: err
+      else
+        res.send credentials

 module.exports = handler


client: auto redirect pricing to default route

--- client/pricing/lib/routehandler.coffee
+++ client/pricing/lib/routehandler.coffee
@@ -5,6 +5,8 @@
 module.exports = ->

   lazyrouter.bind 'pricing', (type, info, state, path, ctx) ->
+
+    return ctx.handleRoute ctx.getDefaultRoute()

     { appManager } = kd.singletons


Fix potential XSS in deploy poller

Fix potential XSS in deploy poller

--- app/assets/javascripts/deploy.coffee
+++ app/assets/javascripts/deploy.coffee
@@ -39,10 +39,24 @@
     return unless chunks.length

     text = chunks.map((c) -> c.text).join('')
-    @$code.append(@colorize(text))
+    @$code.append(@colorize(@escapeHTML(text)))

   colorize: (text) ->
     ansi_up.ansi_to_html(text)
+
+
+  ENTITIES = {
+    '&': '&'
+    '<': '<'
+    '>': '>'
+    '"': '"'
+    "'": '''
+  }
+  ESCAPE_PATTERN = new RegExp("[#{Object.keys(ENTITIES).join('')}]", 'g')
+
+  escapeHTML: (html) ->
+    html.replace ESCAPE_PATTERN, (char) ->
+      ENTITIES[char]

 jQuery ->
   poller = new ChunkPoller($('body'), $('code').data('next-chunks-url'))


Tests: Environments: VM: Fixed turnOffVm test.

--- client/test/lib/environments/vm.coffee
+++ client/test/lib/environments/vm.coffee
@@ -2,27 +2,28 @@
 assert  = require 'assert'
 environmentHelpers = require '../helpers/environmenthelpers.js'

-modalSelector = '.activity-modal.vm-settings'
+modalSelector = '.machine-settings-modal.AppModal'

 module.exports =

   turnOffVm: (browser) ->

-    linkSelector  = modalSelector + ' .statustoggle .input-wrapper'
+    linkSelector  = modalSelector + ' .AppModal-form'
+    vmStateModal  = '.env-machine-state .kdmodal-content'

     helpers.beginTest(browser)
     helpers.waitForVMRunning(browser)

-    environmentHelpers.openVmSettingsModal(browser)
+    environmentHelpers.openGeneralSettings(browser)

     browser
       .waitForElementVisible   linkSelector, 20000
-      .waitForElementVisible   linkSelector + ' .koding-on-off a.knob', 20000
-      .click                   linkSelector + ' .koding-on-off a.knob'
-      .waitForElementVisible   '.env-machine-state .kdmodal-content .state-label.stopping', 20000
-      .waitForElementVisible   '.env-machine-state .kdmodal-content .state-label.stopped', 300000
-      .waitForElementVisible   '.env-machine-state .kdmodal-content .turn-on.state-button', 20000 # Assertion
+      .waitForElementVisible   linkSelector + ' .statustoggle', 20000
+      .click                   linkSelector + ' .statustoggle .koding-on-off.on'
+      .waitForElementVisible   vmStateModal + ' .state-label.stopping', 20000
+      .waitForElementVisible   vmStateModal + ' .state-label.stopped', 300000
+      .waitForElementVisible   vmStateModal + ' .turn-on.state-button', 20000 # Assertion
       .end()


code style tweaks

code style tweaks

--- app/assets/javascripts/dom.js.coffee
+++ app/assets/javascripts/dom.js.coffee
@@ -2,9 +2,9 @@
   el: 'body'

   events:
-    'ajax:complete'                 : 'ajaxComplete'
     'mouseover .product-widget'     : 'widgetMouseover'
     'mouseout .product-widget'      : 'widgetMousetout'
+    'ajax:complete'                 : 'ajaxComplete'

   # widgetMouseout - handle when a user mouses over a product widget.
   widgetMouseover: (ev) ->


fix incorrect endpoint for bulk upload

fix incorrect endpoint for bulk upload

--- app/assets/javascripts/upload.coffee
+++ app/assets/javascripts/upload.coffee
@@ -399,7 +399,7 @@

   # target the endpoint for shrine uploader
   uppy.use Uppy.XHRUpload,
-    endpoint: '/corrections/upload'
+    endpoint: '/packages/upload'
     fieldName: 'file'

   # add metadata to manuscript card if upload was successful


adding arrows to links

adding arrows to links

--- app/coffeescripts/MapLinkView.coffee
+++ app/coffeescripts/MapLinkView.coffee
@@ -4,7 +4,8 @@
     #Instantiate a directions service.
     renderOptions = {
       map: window.map,
-      markerOptions: {visible: false}
+      markerOptions: {visible: false},
+      preserveViewport: true
     }
     this.directionsService = new google.maps.DirectionsService()
     this.directionsDisplay = new google.maps.DirectionsRenderer(renderOptions)
@@ -18,7 +19,7 @@
     request = {
       origin: this.begin,
       destination: this.end,
-      travelMode: google.maps.TravelMode.DRIVING
+      travelMode: google.maps.TravelMode.DRIVING,
     }
     #Route the directions and pass the response to a
     #function to draw the full link for each step.
@@ -34,8 +35,9 @@
   displayArrow: ->
     dir=((Math.atan2(this.end.lng()-this.begin.lng(),this.end.lat()-this.begin.lat())*180)/Math.PI)+360
     ico=((dir-(dir%3))%120)
+    self = this
     new google.maps.Marker({
-      position: this.begin,
+      position: self.getArrowPosition(),
       icon: new google.maps.MarkerImage('http://maps.google.com/mapfiles/dir_'+ico+'.png',
                                  new google.maps.Size(24,24),
                                  new google.maps.Point(0,0),
@@ -44,3 +46,18 @@
       map: window.map
     });

+  getArrowPosition: ->
+    dx = this.end.lng()-this.begin.lng()
+    dy = this.end.lat()-this.begin.lat()
+
+    if (Math.abs(this.end.lng() - this.begin.lng()) > 180.0)
+      dx = -dx
+
+    sl = Math.sqrt((dx*dx)+(dy*dy))
+    theta = Math.atan2(-dy,dx)
+
+    #just put one arrow in the middle of the line
+    x = this.begin.lng() + ((sl/2) * Math.cos(theta))
+    y = this.begin.lat() - ((sl/2) * Math.sin(theta))
+
+    new google.maps.LatLng(x,y)


fixing issue with IE9 signin

fixing issue with IE9 signin

Former-commit-id: c14a4414ec7403f0454c6f31860377c5a14ea6aa [formerly d9cd290a86003f32500d901a589849a8d338f5e7] [formerly 9ccaa362a8e8bd8cbb0e09f821ccfc488064df48 [formerly 9ccaa362a8e8bd8cbb0e09f821ccfc488064df48 [formerly 99c59bcefa0932c57720253f35a552803baa8f6e]]]
Former-commit-id: fe62777f52462184827d6f3beacf0013e74afa74 [formerly d6dcc231cfde2a617a1584f0b6e37a0f0b0f5b07]
Former-commit-id: f99594c0e9d80dbae7df5731fb564f2e685b05c1
Former-commit-id: 2de75470e14dd398c647dfd06f56dae317aac95f

--- app/assets/javascripts/backbone/entities/user.coffee
+++ app/assets/javascripts/backbone/entities/user.coffee
@@ -250,7 +250,8 @@
     set_current_user : (user_id) ->
       @current_user = App.request 'user', user_id
       if !(@current_user instanceof Entities.OperatingUser)
-        @current_user.__proto__ = Entities.OperatingUser.prototype
+        _.extend @current_user, Entities.OperatingUser.prototype
+
       @current_user

     set_fixed_user : (user_data) ->
@@ -280,6 +281,7 @@
       current_user = @current_user

       current_user.set user_data.user
+
       current_user.setFollows user_data.follows if 'follows' of user_data

       if current_user.get 'b64_thumbnail'


Update init_charts.js.coffee

Update init_charts.js.coffee

--- app/assets/javascripts/general/init_charts.js.coffee
+++ app/assets/javascripts/general/init_charts.js.coffee
@@ -30,9 +30,9 @@
   2: 'HPV-PAR-SENT-SUBNV'
   3: 'HPV-PAR-SENT'
   4: 'HPV-TOP'
-  5: 'HPV-PAR-SENT-TOP'
+  5: 'HPV-TOP-PAR-SENT'
   6: 'HPV-PAR'
-  7: 'HPV-PAR-TOP'
+  7: 'HPV-TOP-PAR'

 chartTextDeletions = ['tfid_features=0 ', 'classifier_c=0.0195 ', 'classifier_penalty=l2 ', '_total',
                       'algorithm_version=10.0 ']


Palette: Default to gray if there are no colors left.

--- app/assets/javascripts/pgdatagraph/palette.js.coffee
+++ app/assets/javascripts/pgdatagraph/palette.js.coffee
@@ -31,11 +31,15 @@
     violetRed:
       stroke: "#b42b58"
       fill: "#ed789e"
+  fallbackColor:
+    stroke: "#ccc"
+    fill: "#ccc"

   color: ->
     unused = _.compact _(@colors).map (colors, name) =>
       name unless @colors[name].used
-    @colors[unused[0]].used = yes
-    @colors[unused[0]]
-
-
+    if unused[0]
+      @colors[unused[0]].used = yes
+      @colors[unused[0]]
+    else
+      @fallbackColor


Fixes removing the location badge with backspace on the last character

--- app/assets/javascripts/search_autocomplete.js.coffee
+++ app/assets/javascripts/search_autocomplete.js.coffee
@@ -36,6 +36,8 @@

     @searchInput.addClass('disabled')

+    @saveTextLength()
+
     @bindEvents()

   # Finds an element inside wrapper element
@@ -44,6 +46,9 @@

   saveOriginalState: ->
     @originalState = @serializeState()
+
+  saveTextLength: ->
+    @lastTextLength = @searchInput.val().length

   createAutocomplete: ->
     @searchInput.glDropdown
@@ -65,6 +70,8 @@
     # Prevent multiple ajax calls
     return if @loadingSuggestions

+    return if @badgePresent()
+
     @loadingSuggestions = true

     jqXHR = $.get(@autocompletePath, {
@@ -102,10 +109,11 @@
       scope: @scopeInputEl.val()

       # Location badge
-      _location: $.trim(@locationText.text())
+      _location: @locationText.text()
     }

   bindEvents: ->
+    @searchInput.on 'keydown', @onSearchInputKeyDown
     @searchInput.on 'keyup', @onSearchInputKeyUp
     @searchInput.on 'click', @onSearchInputClick
     @searchInput.on 'focus', @onSearchInputFocus
@@ -123,12 +131,19 @@
   onDropdownOpen: (e) =>
     @dropdown.dropdown('toggle')

+  onSearchInputKeyDown: =>
+    # Saves last length of the entered text
+    @saveTextLength()
+
   onSearchInputKeyUp: (e) =>
     switch e.keyCode
       when KEYCODE.BACKSPACE
-        if e.currentTarget.value is ''
-          @removeLocationBadge()
-          @searchInput.focus()
+        # when trying to remove the location badge
+        if @lastTextLength is 0 and @badgePresent()
+            @removeLocationBadge()
+
+        # When removing the last character and no badge is present
+        if @lastTextLength is 1 and !@badgePresent()
           @disableAutocomplete()
       when KEYCODE.ESCAPE
         if @badgePresent()


Delete classy-bind.coffee

Delete classy-bind.coffee

--- classy-bind.coffee
+++ classy-bind.coffee
@@ -1,23 +0,0 @@
-bind_module = angular.module 'classy-bind', ['classy-core']
-
-bind_module.classy.plugin.controller
-    name: 'bind'
-
-    options:
-        enabled: true
-
-    isActive: (klass, deps) ->
-        if @options.enabled and angular.isObject(klass.bind)
-            if !deps.$element
-                throw new Error "You need to inject `$element` to use the watch object"
-                return false
-
-            return true
-
-    postInit: (klass, deps, module) ->
-        if !@isActive(klass, deps) then return
-
-
-        for key, fn of klass.bind
-            deps.$element.bind key, angular.bind klass, fn
-        return


Automatically submit to ladder the first time for ladder levels

--- app/views/play/level/tome/CastButtonView.coco.coffee
+++ app/views/play/level/tome/CastButtonView.coco.coffee
@@ -113,7 +113,10 @@
       @playSound 'cast-end', 0.5 unless @options.level.isType('game-dev')
       # Worked great for live beginner tournaments, but probably annoying for asynchronous tournament mode.
       myHeroID = if me.team is 'ogres' then 'Hero Placeholder 1' else 'Hero Placeholder'
-      if @autoSubmitsToLadder and not e.world.thangMap[myHeroID]?.errorsOut and not me.get('anonymous')
+      shouldAutoSubmit = @autoSubmitsToLadder or (@options.level.isType('ladder') and not @options.session.get('submitDate') and not @autosubmittedOnce)
+      shouldAutoSubmit &&= not e.world.thangMap[myHeroID]?.errorsOut and not me.get('anonymous')
+      if shouldAutoSubmit
+        @autosubmittedOnce = true
         _.delay (=> @ladderSubmissionView?.rankSession()), 1000 if @ladderSubmissionView
     @hasCastOnce = true
     @updateCastButton()


Define Handlebars if rendering engine is blaze

--- client/boot.coffee
+++ client/boot.coffee
@@ -66,6 +66,10 @@
 # Register Global Helpers
 #

+if Package.ui
+  ->
+    Handlebars = Package.ui.Handlebars
+
 Handlebars.registerHelper "blogFormatDate", (date) ->
   moment(new Date(date)).format "MMM Do, YYYY"


Add right margin to login/profile box

Add right margin to login/profile box

--- resources/assets/coffee/_classes/nav2.coffee
+++ resources/assets/coffee/_classes/nav2.coffee
@@ -49,7 +49,7 @@
     popupRect = popup.getBoundingClientRect()
     popupContainerRect = popup.parentElement.getBoundingClientRect()
     referenceRect = reference.getBoundingClientRect()
-    bodyRect = document.body.getBoundingClientRect()
+    bodyWidth = document.body.getBoundingClientRect().width - 10 # some right margin

     referenceHalfWidth = referenceRect.width / 2
     popupHalfWidth = popupRect.width / 2
@@ -62,7 +62,7 @@
       finalLeft = Math.floor(referenceRect.left) * -1
     else
       popupRightWhenCentered = popupLeftWhenCentered + popupRect.width
-      popupRightOverflow = Math.round(popupRightWhenCentered - bodyRect.width)
+      popupRightOverflow = Math.round(popupRightWhenCentered - bodyWidth)

       finalLeft =
         if popupRightOverflow > 0


cleanup

cleanup

--- apps/editorial_features/components/eoy/client.coffee
+++ apps/editorial_features/components/eoy/client.coffee
@@ -135,7 +135,6 @@

   firstSectionInView: =>
     $('.article-body section[data-section="1"]').waypoint () ->
-      # $(this).find('.article-body--section').toggleClass('active')
       $('.eoy-feature__background').toggleClass('active')
     , {offset: '40%'}


emitChange after load

emitChange after load


ComputeProviders:DigitalOcean: create function implemented

--- workers/social/lib/social/models/computeproviders/digitalocean.coffee
+++ workers/social/lib/social/models/computeproviders/digitalocean.coffee
@@ -6,4 +6,34 @@
     callback null, "DigitalOcean is better #{ client.r.account.profile.nickname }!"

   @fetchExisting = (client, options, callback)->
-    callback null, []+    callback null, []
+
+  @create = (client, options, callback)->
+
+    { credential, name } = options
+
+    @fetchCredentialData credential, (err, credential)->
+
+      return callback err  if err?
+
+      meta =
+        {
+          "variables": {
+            "do_client_id": credential.clientId ? "",
+            "do_api_key": credential.apiKey ? "",
+            "klient_deb": "klient_0.0.1_amd64.deb"
+          },
+          "builders": [
+            {
+              "type": "digitalocean",
+              "client_id": "{{user `do_client_id`}}",
+              "api_key": "{{user `do_api_key`}}",
+              "image": "ubuntu-13-10-x64",
+              "region": "ams1",
+              "size": "512mb",
+              "snapshot_name": "koding-{{timestamp}}"
+            }
+          ]
+        }
+
+      callback null, { meta }


Automatically reload when provisioning Academica courses

This avoids a race condition where the CourseInstances are loaded but
the Classroom isn't.

--- app/views/courses/CoursesView.coffee
+++ app/views/courses/CoursesView.coffee
@@ -230,6 +230,13 @@
       @ownerNameMap[ownerID] = NameLoader.getName(ownerID) for ownerID in ownerIDs
       @render?()
     )
+
+    academicaCS1CourseInstance = _.find(@courseInstances.models ? [], (ci) -> ci.get('_id') is '610047c74bc544001e26ea12')
+    if academicaCS1CourseInstance
+      academicaGlobalClassroom = _.find(@classrooms.models ? [], (c) -> c.get('_id') is '610047c673801a001f85fd43')
+      if not academicaGlobalClassroom and utils.getQueryVariable('autorefresh') isnt true
+        # Refresh so that we make sure we get this loaded
+        window.location.href = '/students?autorefresh=true'

     if not @classrooms.models.length
       @nextLevelInfo = courseAcronym: 'CS1'  # Don't both trying to figure out the next level for edge case of student with no classrooms


Deferred require for testing + imagesLoaded

--- components/merry_go_round/index.coffee
+++ components/merry_go_round/index.coffee
@@ -1,5 +1,4 @@
 _ = require 'underscore'
-Flickity = require 'flickity-imagesloaded'
 Backbone = require 'backbone'
 template = -> require('./templates/navigation.jade') arguments...

@@ -47,6 +46,8 @@
     super

 module.exports = ($el, options = {}) ->
+  Flickity = require 'flickity-imagesloaded'
+
   $flickity = $el.find(options.selector or '.js-mgr-cells')

   totalWidth = _.reduce $flickity.find(options.cellSelector or '.js-mgr-cell'), (memo, el) ->


Hacks around race condition in setup

Hacks around race condition in setup

--- src/desktop/components/article/client/super_article.coffee
+++ src/desktop/components/article/client/super_article.coffee
@@ -63,6 +63,10 @@
   setWaypoints: ->
     return unless @$stickyHeader.length

+    # HACK: `globalClientSetup` which requires this library is somehow being
+    # called *after* this view method, causing this to error.
+    require("jquery-waypoints/waypoints.js")
+
     @$(".article-content").waypoint (direction) =>
       if direction == 'down'
         @$stickyHeader.addClass 'visible'


Remove leftover comment

Remove leftover comment

--- server.coffee
+++ server.coffee
@@ -58,7 +58,6 @@
       if @_isReactive()
         ordered = @skip || (@limit && !@sort)
         callbacks = if ordered then callbacksOrdered else callbacksUnordered
-        # callbacks = callbacksUnordered
         @_depend callbacks

       originalMethod.apply @, args


Add debug to auth controller

Add debug to auth controller

--- src/controllers/authenticate-controller.coffee
+++ src/controllers/authenticate-controller.coffee
@@ -1,6 +1,7 @@
 redis             = require 'redis'
 Authenticator     = require '../models/authenticator'
 MeshbluAuthParser = require '../helpers/meshblu-auth-parser'
+debug = require('debug')('meshblu-http-server:authenticate-controller')

 class AuthenticateController
   constructor: (options={}, dependencies={}) ->
@@ -17,6 +18,7 @@
     return response.status(401).end() unless uuid?

     @authenticator.authenticate uuid, token, (error, isAuthenticated) =>
+      debug '@authenticator.authenticate', error
       return response.status(502).end() if error?
       return response.status(403).end() unless isAuthenticated
       response.status(204).end()


tidying: mostly require things

tidying: mostly require things

--- code/serv.coffee
+++ code/serv.coffee
@@ -1,8 +1,9 @@
 #!/usr/bin/env coffee

-# serv.ps
+# serv.coffee
 # http server for cobalt.

+{exec}  = require 'child_process'
 fs      = require 'fs'
 path    = require 'path'
 # Avoids warning.  Remove when we definitely use a node version with
@@ -11,15 +12,14 @@
 child_process = require 'child_process'
 os      = require 'os'

+bcrypt = require 'bcrypt'
 express = require 'express'
+mongoose = require 'mongoose'
 request = require 'request'
-exec    = require('child_process').exec
-mongoose = require 'mongoose'
-bcrypt = require 'bcrypt'
-
-User = require 'models/user'
+
 Box = require 'models/box'
 SSHKey = require 'models/ssh_key'
+User = require 'models/user'

 app = express()


Fixing typo in purchase receipt

Fixing typo in purchase receipt

--- apps/artwork_purchase/helpers.coffee
+++ apps/artwork_purchase/helpers.coffee
@@ -13,7 +13,7 @@
     p1 = user.name + ' submitted a request to purchase ' + titleAndDate + ' by ' + artwork.artist_names + '.'

     p2 = "Please confirm the work's availability and details, and provide \
-      a shipping estimate to the below address. We ask you to reply witin 3 business \
+      a shipping estimate to the below address. We ask you to reply within 3 business \
       days, and collectors to confirm purchase within 72 hours after receiving a quote. \
       Please note that the collector will review your quote before agreeing to purchase \
       the work and notify you of the final decision."


diagnositcs button hooked up

diagnositcs button hooked up

--- coffee/main.coffee
+++ coffee/main.coffee
@@ -952,6 +952,18 @@

   showDiagnostics: () =>
     console.log("Showing Diagnostics")
+    h = window.innerHeight
+
+    dh = parseInt($("#diagnosticsPanel").css("height").replace('px', ''))
+    if dh > 0
+      $("#diagnosticsPanel").css("top", h+"px")
+      $("#hsplitter").css("top", "auto")
+    else
+      $("#diagnosticsPanel").css("top", (h-200)+"px")
+      $("#hsplitter").css("top", (h-205)+"px")
+      $("#diagnosticsPanel").css("height", "auto")
+
+

 class SplitView
   constructor: (@ve) ->


Utilize the callback that passed to renderFunction

--- src/helpers/amd.coffee
+++ src/helpers/amd.coffee
@@ -25,10 +25,16 @@
 		parts.push "[#{ paths.join "," }]" if paths.length

 		renderFunction = """
-			function (locals) {
+			function (locals, callback) {
 				var rendered;

 				dust.render(<%= template_name %>, locals, function(err, result) {
+					if(typeof callback === "function") {
+						try {
+							callback(err, result);
+						} catch(e) {}
+					}
+
 					if (err) {
 						throw err
 					} else {


ArrayPorts need a connect method too

ArrayPorts need a connect method too

--- src/lib/ArrayPort.coffee
+++ src/lib/ArrayPort.coffee
@@ -7,6 +7,17 @@
     attach: (socket) ->
         @sockets.push socket
         @attachSocket socket
+
+    connect: (socketId = null) ->
+        if socketId is null
+            @sockets.forEach (socket) ->
+                socket.disconnect()
+            return
+
+        unless @sockets[socketId]
+            throw new Error "No socket '#{socketId}' available"
+
+        @sockets[socketId].disconnect()

     beginGroup: (group, socketId = null) ->
         if socketId is null