MachineState: while updating status take error status in action

--- client/ide/views/modals/machinestatemodal.coffee
+++ client/ide/views/modals/machinestatemodal.coffee
@@ -32,14 +32,14 @@
     computeController.on "stop-#{@machineId}",  @bound 'updateStatus'
     computeController.on "error-#{@machineId}", =>
       @hasError = yes
-      @updateStatus { status: Unknown }
+      @updateStatus status: Unknown

     @show()

   updateStatus: (event) ->

-    {status, percentage} = event
+    {status, percentage, error} = event

     if status is @state
       if percentage?
@@ -48,6 +48,8 @@

     else
       @state = status
+
+      @hasError = error?.length > 0

       if percentage? and percentage is 100
         @progressBar?.updateBar 100


Added: comments

Added: comments

--- server/controllers/album.coffee
+++ server/controllers/album.coffee
@@ -112,9 +112,10 @@
                 else if photo?.binary.file?
                     type = 'file'
                 else
+                    # There is nothing to download for this photo.
                     return cb()

-                name = photo.title or "#{photo.id}.jpg"
+                # Get a stream  of the binary.
                 laterStream = photo.getBinary type, (err) ->
                     if err?
                         log.error "An error occured while adding a photo to
@@ -122,7 +123,10 @@
                         log.raw err
                         cb()

+                # Append the stream photo to the archive.
                 laterStream.on 'ready', (stream) ->
+                    # Get the file's name in the archive.
+                    name = photo.title or "#{photo.id}.jpg"
                     archive.append stream, {name}
                     cb()

@@ -132,7 +136,7 @@
                 # Start the streaming.
                 archive.pipe res

-                # Arbort archiving process when the user aborts his request.
+                # Abort archiving process when the user aborts his request.
                 res.on 'close', ->
                     archive.abort()

@@ -141,10 +145,12 @@
                 res.setHeader 'Content-Disposition', disposition
                 res.setHeader 'Content-Type', 'application/zip'

+                # Append all photos to the archive.
                 async.eachSeries photos, addToArchive, (err) ->
                     if err
                         log.error "An error occured: #{err}"
                     else
+                        # Make the archive as ready.
                         archive.finalize()


Autosize when clicking textarea containing text

--- core/app/backbone/factlink/text_area_view.coffee
+++ core/app/backbone/factlink/text_area_view.coffee
@@ -12,6 +12,7 @@
     'keydown textarea': '_parseKeyDown'
     'keyup textarea': 'updateModel'
     'input textarea': 'updateModel'
+    'click textarea': '_onClick'

   triggers:
     'focus textarea': 'focus'
@@ -61,3 +62,6 @@
     @_initAutosize()
     @model.set 'text', textBefore + text + textAfter
     @focusInput()
+
+  _onClick: ->
+    @_initAutosize() if @model.get('text')


fixes changes

fixes changes

--- kites/sharedHosting/mounter.coffee
+++ kites/sharedHosting/mounter.coffee
@@ -172,7 +172,7 @@
             log.info info = "[OK] config successfully updated"
             callback null, info

-  spawnWrapper = (command, args , callback)->
+  spawnWrapper : (command, args , callback)->
     wrapper = spawn command,args
     wrapperErr = ""
     wrapper.stderr.on 'data',(data)->
@@ -205,7 +205,7 @@

     {username} = options
     args = ['-m', username]
-    spawnWrapper config.cagefsctl, args ,(err, res)->
+    @spawnWrapper config.cagefsctl, args ,(err, res)->
       if err?
         log.error error = "[ERROR] Couldn't remount user's VE - username #{username}: #{stderr}"
         callback error
@@ -214,7 +214,7 @@
         callback null, info

-   createMountpoint = (options, callback)->
+   createMountpoint : (options, callback)->

      # create mount point for remote resource
      # /Users/<username>/RemoteDrive/<remote_hostname>
@@ -263,18 +263,18 @@
       options.mountpoint = path.join config.usersPath, username, config.baseMountDir, remotehost

       # fetch user ID for curlftfs command
-      spawnWrapper '/usr/bin/id', ['-u',username], (err,res)=>
+      @spawnWrapper '/usr/bin/id', ['-u',username], (err,res)=>
         if err?
           log.error error = "[ERROR] Can't find user ID: #{err}"
           callback error
         else
           log.info "[OK] user ID for user #{username} is #{res}"
           ftpfsopts = "#{config.ftpfs.opts},uid=#{res},gid=#{res},fsname=#{remotehost},user=#{remoteuser}:#{remotepass}"
-          createMountpoint options,(err,res)=>
+          @createMountpoint options,(err,res)=>
             if err
               callback err
             else
-              spawnWrapper config.ftpfs.curlftpfs,['-o', ftpfsopts, remotehost, options.mountpoint] , (err, res)=>
+              @spawnWrapper config.ftpfs.curlftpfs,['-o', ftpfsopts, remotehost, options.mountpoint] , (err, res)=>
                 if err?
                   log.error error = "[ERROR] couldn't mount remote FTP server #{remotehost}: #{err}"
                   callback error
@@ -309,11 +309,11 @@
       keyPath = path.join config.usersPath,username,'.ssh','koding.pem'
       sshopts = [ "-o", "ssh_command=/usr/bin/ssh -i #{keyPath} -o #{config.sshfs.optsWithKey},fsname=#{remotehost}", "#{remoteuser}@#{remotehost}:/", options.mountpoint]
       console.log sshopts
-      createMountpoint options, (err, res)=>
+      @createMountpoint options, (err, res)=>
         if err
           callback err
         else
-          spawnWrapper config.sshfs.sshfscmd, sshopts, (err, res)=>
+          @spawnWrapper config.sshfs.sshfscmd, sshopts, (err, res)=>
             if err?
               log.error error = "[ERROR] couldn't mount remote SSH server #{remotehost}: #{err}"
               callback error
@@ -348,7 +348,7 @@

       sshopts = [ "-o", "ssh_command=/usr/bin/ssh -o #{config.sshfs.opts},fsname=#{remotehost}", "#{remoteuser}@#{remotehost}:/", options.mountpoint]
       console.log sshopts
-      createMountpoint options, (err, res)=>
+      @createMountpoint options, (err, res)=>
         if err
           callback err
         else


Let admins play course levels even if they're not in courses yet, for development

--- server/middleware/levels.coffee
+++ server/middleware/levels.coffee
@@ -84,7 +84,9 @@
     if code
       attrs.code = code

-    if level.get('type') in ['course', 'course-ladder'] or req.query.course?
+    if req.user.isAdmin()
+      null  # Admins can play any level, even if it's not in a course yet (example: level editor)
+    else if level.get('type') in ['course', 'course-ladder'] or req.query.course?

       # Find the course and classroom that has assigned this level, verify access
       # Handle either being given the courseInstance, or having to deduce it


removed teamhandlerhelper class

removed teamhandlerhelper class

--- servers/testhelper/index.coffee
+++ servers/testhelper/index.coffee
@@ -110,161 +110,6 @@

   return data

-class TeamHandlerHelper
-
-
-
-  @generateCheckTokenRequestBody = (opts = {}) ->
-
-    defaultBodyObject =
-      token : generateRandomString()
-
-    deepObjectExtend defaultBodyObject, opts
-
-    return defaultBodyObject
-
-
-  @generateCheckTokenRequestParams = (opts = {}) ->
-
-    url  = generateUrl
-      route : '-/teams/validate-token'
-
-    body = TeamHandlerHelper.generateCheckTokenRequestBody()
-
-    params               = { url, body }
-    defaultRequestParams = generateDefaultRequestParams params
-    requestParams        = deepObjectExtend defaultRequestParams, opts
-    # after deep extending object, encodes body param to a query string
-    requestParams.body   = querystring.stringify requestParams.body
-
-    return requestParams
-
-
-  @generateJoinTeamRequestBody = (opts = {}) ->
-
-    username = generateRandomUsername()
-
-    defaultBodyObject =
-      slug                : "testcompany#{generateRandomString(10)}"
-      email               : generateRandomEmail()
-      token               : ''
-      allow               : 'true'
-      agree               : 'on'
-      username            : username
-      password            : 'testpass'
-      redirect            : ''
-      newsletter          : 'true'
-      alreadyMember       : 'false'
-      passwordConfirm     : 'testpass'
-
-    deepObjectExtend defaultBodyObject, opts
-
-    return defaultBodyObject
-
-
-  @generateJoinTeamRequestParams = (opts = {}) ->
-
-    url  = generateUrl
-      route : '-/teams/join'
-
-    body = TeamHandlerHelper.generateJoinTeamRequestBody()
-
-    params               = { url, body }
-    defaultRequestParams = generateDefaultRequestParams params
-    requestParams        = deepObjectExtend defaultRequestParams, opts
-    # after deep extending object, encodes body param to a query string
-    requestParams.body   = querystring.stringify requestParams.body
-
-    return requestParams
-
-
-  @generateGetTeamRequestParams = (opts = {}) ->
-
-    { groupSlug } = opts
-    url  = generateUrl
-      route : "-/team/#{groupSlug}"
-
-    params               = { url }
-    defaultRequestParams = generateDefaultRequestParams params
-    requestParams        = deepObjectExtend defaultRequestParams, opts
-    # after deep extending object, encodes body param to a query string
-    requestParams.body   = querystring.stringify requestParams.body
-
-    return requestParams
-
-
-  @generateGetTeamMembersRequestBody = (opts = {}) ->
-
-    defaultBodyObject =
-      limit : '10'
-      token : ''
-
-    deepObjectExtend defaultBodyObject, opts
-
-    return defaultBodyObject
-
-
-  @generateGetTeamMembersRequestParams = (opts = {}) ->
-
-    { groupSlug } = opts
-    delete opts.groupSlug
-
-    url  = generateUrl
-      route : "-/team/#{groupSlug}/members"
-
-    body = TeamHandlerHelper.generateGetTeamMembersRequestBody()
-
-    params               = { url, body }
-    defaultRequestParams = generateDefaultRequestParams params
-    requestParams        = deepObjectExtend defaultRequestParams, opts
-    # after deep extending object, encodes body param to a query string
-    requestParams.body   = querystring.stringify requestParams.body
-
-    return requestParams
-
-
-  @generateCreateTeamRequestBody = (opts = {}) ->
-
-    username    = generateRandomUsername()
-    invitees    = "#{generateRandomEmail('koding.com')},#{generateRandomEmail('gmail.com')}"
-    companyName = "testcompany#{generateRandomString(10)}"
-
-    defaultBodyObject =
-      slug           :  companyName
-      email          :  generateRandomEmail()
-      agree          :  'on'
-      allow          :  'true'
-      domains        :  'koding.com, gmail.com'
-      invitees       :  invitees
-      redirect       :  ''
-      username       :  username
-      password       :  'testpass'
-      newsletter     :  'true'
-      companyName    :  companyName
-      alreadyMember  :  'false'
-      passwordConfirm:  'testpass'
-
-    deepObjectExtend defaultBodyObject, opts
-
-    return defaultBodyObject
-
-
-  # overwrites given options in the default params
-  @generateCreateTeamRequestParams = (opts = {}) ->
-
-    url  = generateUrl
-      route : '-/teams/create'
-
-    body = TeamHandlerHelper.generateCreateTeamRequestBody()
-
-    params               = { url, body }
-    defaultRequestParams = generateDefaultRequestParams params
-    requestParams        = deepObjectExtend defaultRequestParams, opts
-    # after deep extending object, encodes body param to a query string
-    requestParams.body   = querystring.stringify requestParams.body
-
-    return requestParams
-

 module.exports = {
   generateUrl
@@ -274,6 +119,4 @@
   generateRandomString
   generateRandomUsername
   generateDefaultRequestParams
-
-  TeamHandlerHelper
 }


more dates

more dates

--- src/api/apps/articles/test/model/save.test.coffee
+++ src/api/apps/articles/test/model/save.test.coffee
@@ -22,6 +22,7 @@
     # offset = date.getTimezoneOffset()
     # dateWithOffset = moment(date).add(offset, 'm').toDate();
     sandbox.useFakeTimers(date)
+

   after ->
     @server.close()
@@ -197,7 +198,7 @@
         slugs: ['molly-clockwork']
       })

-    it.only 'appends unix timestamp for current date to the slug if the slug exists already and it is a draft and there is no publish date', (done) ->
+    it 'appends unix timestamp for current date to the slug if the slug exists already and it is a draft and there is no publish date', (done) ->
       x = new Date()
       console.log("date now",x)
       os = x.getTimezoneOffset()


OverlayTrigger becomes Button/Modal on delete period

--- src/components/course-settings/delete-period.cjsx
+++ src/components/course-settings/delete-period.cjsx
@@ -18,14 +18,21 @@

   getInitialState: ->
     warning: ''
+    showModal: false
+
+  close: ->
+    @setState({showModal: false})
+
+  open: ->
+    @setState({showModal: true})

   performUpdate: ->
     if @isPeriodEmpty()
-      @refs.overlay.hide()
       # tab to be deleted cannot be activeTab unless first, so select previous or first
       @props.selectPreviousTab()
       id = @props.activeTab.id
       PeriodActions.delete(id, @props.courseId)
+      @close()
     else
       @setState(warning: EMPTY_WARNING)

@@ -54,8 +61,13 @@

     <BS.Modal
       {...@props}
-      title={title}
+      show={@state.showModal}
+      onHide={@close}
       className="teacher-edit-period-modal">
+
+      <BS.Modal.Header closeButton>
+        <BS.Modal.Title>{title}</BS.Modal.Title>
+      </BS.Modal.Header>

@@ -72,18 +84,13 @@
         {deleteButton if @isPeriodEmpty()}

-
-
     </BS.Modal>

   render: ->
-    <BS.OverlayTrigger
-      ref='overlay'
-      rootClose={true}
-      trigger='click'
-      overlay={@renderForm()}>
-        <BS.Button bsStyle='link' className='edit-period'>
-
-          Delete <CourseGroupingLabel courseId={@props.courseId}/>
-        </BS.Button>
-    </BS.OverlayTrigger>
+
+      <BS.Button onClick={@open} bsStyle='link' className='edit-period'>
+
+        Delete <CourseGroupingLabel courseId={@props.courseId}/>
+      </BS.Button>
+      {@renderForm()}
+


add transform to Object2D

add transform to Object2D

--- src/core/Object2D.coffee
+++ src/core/Object2D.coffee
@@ -4,5 +4,12 @@

 	constructor: () ->
 		Geom2D.Colorful.apply @
+
+		@visible = yes
+
+		@position = new Geom2D.Point()
+		@rotation = 0
+		@scale = new Geom2D.Point(1, 1)
+		@skew = new Geom2D.Point()
+
 		@children = []
-		@stroke = () =>


Fix: リポジトリが開けないのを修正

Fix: リポジトリが開けないのを修正

--- src/core/applyMod.coffee
+++ src/core/applyMod.coffee
@@ -4,7 +4,7 @@

 {app} = require "electron"
 path = require "path"
-Promise = require "Promise"
+Promise = require "promise"
 fs = require "fs-extra"
 fstream = require "fstream"
 jszip = require "jszip"


Limit zooming out to the displaying the whole date range

--- src/d3.timeslider.coffee
+++ src/d3.timeslider.coffee
@@ -61,8 +61,10 @@
                 @options.zoom.on('zoom', null)
             )
             .on('brushend', =>
-                @options.zoom.scale(@options.lastZoom.scale)
-                    .translate(@options.lastZoom.translate).on('zoom', zoom)
+                @options.zoom
+                    .scale(@options.lastZoom.scale)
+                    .translate(@options.lastZoom.translate)
+                    .on('zoom', zoom)
                 element.dispatchEvent(
                     new CustomEvent('selectionChanged', {
                         detail: {
@@ -181,7 +183,10 @@
         zoom = =>
             redraw()

-        @options.zoom = d3.behavior.zoom().x(@scales.x).on('zoom', zoom)
+        @options.zoom = d3.behavior.zoom()
+            .x(@scales.x)
+            .scaleExtent([1, Infinity])
+            .on('zoom', zoom)
         @root.call(@options.zoom)

     # Function pair to allow for easy hiding and showing the time slider


minor name changes

minor name changes

--- library/fringes/tracker.coffee
+++ library/fringes/tracker.coffee
@@ -63,7 +63,7 @@
     # socket events that indicate successful and fail conditions.
     # When either one is happens, it emits the `toastr` notice. A
     # most recent method implementation uses `$root` as a subject.
-    monitoring: @awaiting "socketing", (socket, location) ->
+    tracking: @awaiting "socketing", (socket, location) ->
         assert l = i18n "server connection has been lost"
         assert s = i18n "exception occured on the server"
         assert c = i18n "established connection to server"


change the def name

change the def name

--- library/membrane/visual.coffee
+++ library/membrane/visual.coffee
@@ -276,7 +276,7 @@
     # rule, this method does not have to alter any contents or data
     # of the resource. Use for unobtrusive retrieval of resources.
     GET: (request, response, resource, domain, session) ->
-        symbol = "service".toString().toLowerCase()
+        assert symbol = "$root".toString().toLowerCase()
         assert args = [symbol, request, yes, undefined]
         @assembleContext args..., (context, compiled) ->
             length = do -> compiled.length or undefined


some more surgeries on the ancient codes

--- library/nucleus/restful.coffee
+++ library/nucleus/restful.coffee
@@ -43,7 +43,8 @@
 # and in the end user application that provides a REST interface
 # to some arbitrary resource, determined by HTTP path and guarded
 # by the domain matching. This is the crucial piece of framework.
-# It supports strictly methods defined in the HTTP specification.
+# It supports strictly methods defined in the HTTP specification,
+# yet providing a tiny enough structure to be able to override it.
 module.exports.RestfulService = class RestfulService extends Service

     # This is a marker that indicates to some internal subsystems


don't forget to call super ctor

don't forget to call super ctor

--- library/nucleus/service.coffee
+++ library/nucleus/service.coffee
@@ -62,7 +62,7 @@
     # the kernel instance as a parameter. You can override it as
     # you see fit, but be sure to invoke the super constructor and
     # it is highly advised to store the kernel instance in object.
-    constructor: (@kernel) -> @uuid = uuid.v4()
+    constructor: (@kernel) -> super and @uuid = uuid.v4()

     # Either obtain or set the HTTP location of the current service.
     # This method is a proxy that forwards the invocation to the


surgery ops to the old coding

surgery ops to the old coding

--- library/nucleus/service.coffee
+++ library/nucleus/service.coffee
@@ -50,6 +50,7 @@
 # and processing logic based on domain matches and RE match/extract
 # logics, to deal with paths. Remember that this service is just a
 # an internal base class, you generally should not use it directly.
+# Also, all of its functionality can be overriden by any service.
 module.exports.Service = class Service extends ServiceBillets

     # This is a marker that indicates to some internal subsystems


Fixed the order of firing mod events

Fixed the order of firing mod events

--- library/visualize/trees.coffee
+++ library/visualize/trees.coffee
@@ -207,8 +207,8 @@
         throw new Error invalid unless correct
         throw new Error abstract if node.abstract()
         index = _.findIndex @children or [], isEqual
+        (@children ?= []).splice index, 1 if index?
         @emit "remove", this, node, index if index?
-        (@children ?= []).splice index, 1 if index?

     # Prepend the supplied node to the array of children nodes of
     # this element. The node undergoes some substantial testing
@@ -223,8 +223,8 @@
         throw new Error abstract if node.abstract()
         previous = node.parent(); node.parent this
         exists = _.find @children or [], isEqual
-        @emit "prepend", this, node unless exists?
         (@children ?= []).unshift node unless exists
+        @emit "prepend", this, node unless exists

     # Append the supplied node to the array of children nodes of
     # this element. The node undergoes some substantial testing
@@ -239,5 +239,5 @@
         throw new Error abstract if node.abstract()
         previous = node.parent(); node.parent this
         exists = _.find @children or [], isEqual
-        @emit "append", this, node unless exists?
         (@children ?= []).push node unless exists
+        @emit "append", this, node unless exists


Remove redundant count

Remove redundant count

--- mongodb.coffee
+++ mongodb.coffee
@@ -24,7 +24,6 @@
 					query = Object.keys(db_command.query).sort().join("_")
 					key += "." + query

-				Metrics.inc key
 				timer = new Metrics.Timer(key)
 				start = new Date()
 				_method.call this, db_command, options, () ->


Fix #23 - change to warning in client, correct fix is in server.

--- src/scripts/controllers/mapController.coffee
+++ src/scripts/controllers/mapController.coffee
@@ -236,7 +236,7 @@
       # We merge the misc payload fields into one dictionary - showing the latest combination of all data
       angular.extend(marker.payload, payload)
       p = marker.payload
-      if p.userName? && p.id? && p.vehicleText?
+      if p.userName? && p.id? && p.summaryText?
         avatarImg = if p.userAvatarImage?
           """"""
         else
@@ -258,6 +258,8 @@

           """
+      else
+        @log.warn("Skipping popup generation - not enough vehicle data")

   motionTracking: (vehicleKey, latlng) =>
     v = @scope.vehiclePaths[vehicleKey] ? { color: '#f76944', weight: 7, latlngs: []}


array_extensions

--- src/array_extensions.coffee
+++ src/array_extensions.coffee
@@ -514,6 +514,11 @@
 ###*
 Add all the elements in the array.

+

+[1, 2, 3, 4].sum()
+# => 10
+

+
 @name sum
 @methodOf Array#
 @returns {Number} The sum of the elements in the array.


wrap libs

wrap libs

--- lib/atom-ternjs-config-view.coffee
+++ lib/atom-ternjs-config-view.coffee
@@ -39,13 +39,16 @@
     header.innerHTML = 'libs:'
     wrapper.appendChild(header)
     for lib in libs
+      inputWrapper = document.createElement('div')
+      inputWrapper.classList.add('input-wrapper')
       label = document.createElement('span')
       label.innerHTML = lib.name
       checkbox = document.createElement('input')
       checkbox.type = 'checkbox'
       checkbox.checked = lib.value
-      wrapper.appendChild(label)
-      wrapper.appendChild(checkbox)
+      inputWrapper.appendChild(label)
+      inputWrapper.appendChild(checkbox)
+      wrapper.appendChild(inputWrapper)
     wrapper

   getClose: ->


store suggestions as language:word instead of word

avoid words like 'constructor' from accessing properties of
suggestions object.

--- app/coffee/ASpell.coffee
+++ app/coffee/ASpell.coffee
@@ -11,9 +11,10 @@
 		@runAspellOnWords language, words, (error, output) =>
 			return callback(error) if error?
 			#output = @removeAspellHeader(output)
-			suggestions = @getSuggestions(output)
+			suggestions = @getSuggestions(language, output)
 			results = []
 			hits = 0
+			addToCache = {}
 			for word, i in words
 				key = language + ':' + word
 				cached = cache.get(key)
@@ -25,16 +26,22 @@
 					else
 						results.push index: i, suggestions: cached
 				else
-					if suggestions[word]?
-						cache.set(key, suggestions[word])
-						results.push index: i, suggestions: suggestions[word]
+					if suggestions[key]?
+						addToCache[key] = suggestions[key]
+						results.push index: i, suggestions: suggestions[key]
 					else
 						# a valid word, but uncached
-						cache.set(key, true)
+						addToCache[key] = true
+
+			# update the cache after processing all words, to avoid cache
+			# changing while we use it
+			for k, v in addToCache
+				cache.set(k, v)
+
 			logger.log hits: hits, total: words.length, hitrate: hits/words.length, "cache hit rate"
 			callback null, results

-	getSuggestions: (output) ->
+	getSuggestions: (language, output) ->
 		lines = output.split("\n")
 		suggestions = {}
 		for line in lines
@@ -43,12 +50,12 @@
 				if parts.length > 1
 					word = parts[1]
 					suggestionsString = line.slice(line.indexOf(":") + 2)
-					suggestions[word] = suggestionsString.split(", ")
+					suggestions[language + ":" + word] = suggestionsString.split(", ")
 			else if line[0] == "#" # No suggestions
 				parts = line.split(" ")
 				if parts.length > 1
 					word = parts[1]
-					suggestions[word] = []
+					suggestions[language + ":" + word] = []
 		return suggestions

 	#removeAspellHeader: (output) -> output.slice(1)


removed not useful math_spec file

removed not useful math_spec file

--- math/math_spec.coffee
+++ math/math_spec.coffee
@@ -1,27 +0,0 @@
-#= require lib/math
-
-describe "Math Library", ->
-  describe "buttons", ->
-    beforeEach ->
-      @btn_lib = ttm.require 'lib/math/buttons'
-      @buttons = @btn_lib.makeBuilder element: f()
-    describe "variables", ->
-      beforeEach ->
-        @variable = {name: "face", variable_identifier: 124}
-        @btn = @buttons.variables
-          variables: [@variable]
-
-      it "renders a set of variables", ->
-        _(@btn).each (button)->
-          button.render()
-        expect(f().text()).toMatch /face/
-
-      it "sends its passed object along to its clicked handler", ->
-        spy = jasmine.createSpy('variable_btn_click')
-        _(@btn).each (button)->
-          button.render(click: spy)
-        f().find('button').first().click()
-
-        expect(spy.calls[0].args[0].variable).toEqual @variable
-
-


Social: do not modify KD data

Social: do not modify KD data

--- client/Main/socialapicontroller.coffee
+++ client/Main/socialapicontroller.coffee
@@ -7,12 +7,13 @@
     KD.getSingleton("mainController").ready @bound "openGroupChannel"

   getPrefetchedData:->
-    return {} unless KD.socialApiData
-    KD.socialApiData["popularTopics"] = mapChannels KD.socialApiData["popularTopics"]
-    KD.socialApiData["followedChannels"] = mapChannels KD.socialApiData["followedChannels"]
-    KD.socialApiData["pinnedMessages"] = mapActivities KD.socialApiData["pinnedMessages"]
-    KD.socialApiData["privateMessages"] = mapPrivateMessages KD.socialApiData["privateMessages"]
-    return KD.socialApiData
+    data = {}
+    return data unless KD.socialApiData
+    data["popularTopics"]    = mapChannels KD.socialApiData["popularTopics"]
+    data["followedChannels"] = mapChannels KD.socialApiData["followedChannels"]
+    data["pinnedMessages"]   = mapActivities KD.socialApiData["pinnedMessages"]
+    data["privateMessages"]  = mapPrivateMessages KD.socialApiData["privateMessages"]
+    return data

   openGroupChannel: ->
     # to - do refactor this part to use same functions with other parts


Add comments to MessageStore

Add comments to MessageStore

--- client/app/stores/message_store.coffee
+++ client/app/stores/message_store.coffee
@@ -510,9 +510,23 @@
     getCurrentConversationID: ->
         return _currentCID

+    ###*
+    * Get older conversation displayed before current
+    *
+    * @param {Function}  transform
+    *
+    * @return {List}
+    ###
     getPreviousConversation: (param={}) ->
         @getConversation transform: (index) -> ++index

+    ###*
+    * Get earlier conversation displayed after current
+    *
+    * @param {Function}  transform
+    *
+    * @return {List}
+    ###
     getNextConversation: (param={}) ->
         @getConversation transform: (index) -> --index

@@ -530,6 +544,19 @@
                 message.get('conversationID') is conversationID
             .sort reverseDateSort

+    ###*
+    * Get Conversation
+    *
+    * If none parameters    return current conversation
+    * @param.transform      return the list index needed
+    * @param.type="message" return the closest message
+    *                       instead of conversation
+    *
+    * @param {String}   type
+    * @param {Function} transform
+    *
+    * @return {List}
+    ###
     getConversation: (param={}) ->
         @setConversation param.conversationID if param.conversationID?
         return _conversationMemoize unless _.isFunction param.transform


update buildlogs: create and destroy views for each machine

--- client/home/lib/buildlogs/index.coffee
+++ client/home/lib/buildlogs/index.coffee
@@ -14,28 +14,28 @@

   constructor: (options = {}, data) ->

-    { computeController, router } = kd.singletons
-
-    path = router.getCurrentPath()
-
-    machineUid = path.split('/').pop()
-
     options.cssClass = kd.utils.curry 'Build-Logs', options.cssClass

     super options, data

-    machine = computeController.findMachineFromMachineUId machineUid

+  handleAction: (uid) ->
+
+    @destroyViews()
+
+    { computeController } = kd.singletons
+
+    machine = computeController.findMachineFromMachineUId uid
+    path = ''
     return router.handleNotFound path  unless machine

-    @machineName = machine.label
+    machineName = machine.label

     # Path of cloud-init-output log
     path = '/var/log/cloud-init-output.log'
-    @file = FSHelper.createFileInstance { path, machine }
+    file = FSHelper.createFileInstance { path, machine }

-
-    @file.fetchPermissions (err, result) ->
+    file.fetchPermissions (err, result) =>

       return showErrorNotification err  if err

@@ -51,8 +51,10 @@
         IDEHelpers.showFileAccessDeniedError()
         return router.handleRoute '/Home/Stacks/virtual-machines'

+      @addViews file, machineName

-  viewAppended: ->
+
+  addViews: (file, machineName) ->

     @addSubView @header = header()

@@ -62,11 +64,19 @@
       click : ->
         kd.singletons.router.handleRoute '/Home/Stacks/virtual-machines'

-    @addSubView new kd.CustomHTMLView
+    @addSubView @backLink = new kd.CustomHTMLView
       cssClass : 'pane-header'
       partial : """
-
 #{@machineName} Build Logs

+
 #{machineName} Build Logs

 /var/log/cloud-init-output.log

       """

-    @addSubView new IDETailerPane { cssClass: 'build-logs-view', @file, delegate: this }
+    @addSubView @tailer = new IDETailerPane { cssClass: 'build-logs-view', file, delegate: this }
+
+
+  destroyViews: ->
+
+    @header?.destroy()
+    @backLink?.destroy()
+    @tailer?.destroy()
+


Tests: Helpers: Increase timers.

Tests: Helpers: Increase timers.

--- client/test/lib/helpers/helpers.coffee
+++ client/test/lib/helpers/helpers.coffee
@@ -63,18 +63,18 @@
   doLogout: (browser) ->

     browser
-      .waitForElementVisible  '[testpath=AvatarAreaIconLink]', 10000
+      .waitForElementVisible  '[testpath=AvatarAreaIconLink]', 30000
       .click                  '[testpath=AvatarAreaIconLink]'
       .click                  '[testpath=logout-link]'
       .pause                  3000
-      .waitForElementVisible  '[testpath=main-header]', 10000 # Assertion
+      .waitForElementVisible  '[testpath=main-header]', 30000 # Assertion

   attemptEnterEmailAndPasswordOnRegister: (browser, user) ->

     browser
       .url                    @getUrl()
-      .waitForElementVisible  '[testpath=main-header]', 10000
+      .waitForElementVisible  '[testpath=main-header]', 30000
       .setValue               '[testpath=register-form-email]', user.email
       .setValue               'input[name=password]', user.password
       .click                  '[testpath=signup-button]'
@@ -85,7 +85,7 @@
     modalSelector  = '.extra-info.password'
     buttonSelector = 'button[type=submit]'

-    browser.waitForElementVisible modalSelector, 10000
+    browser.waitForElementVisible modalSelector, 30000

     if user.gravatar
       browser
@@ -165,7 +165,7 @@
     browser
       .pause                    2500 # while typing something steals activity input focus
       .click                    '[testpath="public-feed-link/Activity/Topic/public"]'
-      .waitForElementVisible    '[testpath=ActivityInputView]', 10000
+      .waitForElementVisible    '[testpath=ActivityInputView]', 30000
       .click                    '[testpath="ActivityTabHandle-/Activity/Public/Recent"] a'
       .waitForElementVisible    '.most-recent [testpath=activity-list]', 30000
       .click                    '[testpath=ActivityInputView]'


this renders the image properly without skewing sy away from data space

--- bokehjs/src/coffee/renderer/glyph/image.coffee
+++ bokehjs/src/coffee/renderer/glyph/image.coffee
@@ -26,9 +26,6 @@
       super(options)

     _set_data: (@data) ->
-      for i in [0...@y.length]
-        @y[i] += @dh[i]
-
       if not @image_data? or @image_data.length != @image.length
         @image_data = new Array(@image.length)

@@ -78,7 +75,7 @@
         if isNaN(@sx[i] + @sy[i] + @sw[i] + @sh[i])
           continue

-        y_offset = @sy[i]+@sh[i]/2
+        y_offset = @sy[i]

         ctx.translate(0, y_offset)
         ctx.scale(1, -1)


Added test modules

Added test modules

--- test/keydown.coffee
+++ test/keydown.coffee
@@ -1,15 +1,19 @@
+module "keydown"
+
 test "keydown", ->
   event = $.Event("keydown")
   event.which = 32

   $(document).trigger event
-
+
   equal(keydown.space, true, "Space should be down after a keydown event")
-
+
   event = $.Event("keyup")
   event.which = 32
-
+
   $(document).trigger event
-
+
   equal(keydown.space, false, "Space should be up after a keyup event")

+module()
+


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/metered.coffee
+++ test/metered.coffee
@@ -19,6 +19,7 @@
   equals obj.I.maxHealth, 150

 test "should set both <attribute> and max<attribute> if they aren't present in the including object", ->
-  obj = Ga
+  obj = GameObject
+

 module()


Fixed tests for DWebp::png

Fixed tests for DWebp::png

--- test/methods.coffee
+++ test/methods.coffee
@@ -50,15 +50,19 @@
         args = generate_args aliace, type
         webp = (new Webp filename)[name] args...
         write(webp, 'out.json').then (data) ->
-          data.should.have.keys key, '_', '__', 'o'
+          if key is '-'
+            data.should.have.keys '_', '__', 'o'
+            data.__.should.not.containEql "-#{key}"
+          else
+            data.should.have.keys key, '_', '__', 'o'
+            if type is 'boolean'
+              data[key].should.be.ok
+            else
+              data[key].should.be.equal args[0]
+              for arg in args[1...]
+                data._.should.containEql arg
+            data.__.should.containEql "-#{key}"
           data._.should.containEql filename
-          if type is 'boolean'
-            data[key].should.be.ok
-          else
-            data[key].should.be.equal args[0]
-            for arg in args[1...]
-              data._.should.containEql arg
-          data.__.should.containEql "-#{key}"

       unless type is 'boolean'
         it 'should throw type exceprions', ->
@@ -102,8 +106,11 @@
             webp[method]()
           webp[name]()
           write(webp, 'out.json').then (data) ->
-            data.should.have.keys key, '_', '__', 'o'
-            data[key].should.be.ok
+            if key is '-'
+              data.should.have.keys '_', '__', 'o'
+            else
+              data.should.have.keys key, '_', '__', 'o'
+              data[key].should.be.ok

       it 'should have description', (done) ->
         filename = Math.random().toString(36)


One test; viz., for binary literal.

One test; viz., for binary literal.

--- test/numbers.coffee
+++ test/numbers.coffee
@@ -14,9 +14,7 @@
 # Binary notation is understood as would be decimal notation.

 test "Parser recognises binary numbers", ->
-  eq 4, 0b100.valueOf()
-  eq '11', 0b100.toString 3
-  eq '100', 0b100['toString'] 2
+  eq 4, 0b100

 # Decimal Integer Literals


Use a button to disable

Use a button to disable

--- index.cjsx
+++ index.cjsx
@@ -11,6 +11,7 @@
   extension: '.json'
 window.i18n.secretary.setLocale(window.language)
 __ = window.i18n.secretary.__.bind(window.i18n.secretary)
+__r = window.i18n.resources.__.bind(window.i18n.resources)

 # constant
 SERVERS = [
@@ -56,10 +57,11 @@
   version: '0.0.0'  # See package.json
   reactClass: React.createClass
     getInitialState: ->
-      isLogin: false
+      # 0=fleet secretary, -1=disable, 1~*=$ships[]
       notifySecretary: config.get('plugin.secretary.ship', 0)
       fleetSecretary: null
       # Game data
+      isLogin: false
       ships: []
       shipgraph: []
     componentDidMount: ->
@@ -87,42 +89,35 @@
         when '/kcsapi/api_port/port', '/kcsapi/api_get_member/deck', '/kcsapi/api_req_hensei/change'
           {_decks, _ships} = window
           ship = _decks[0]?.api_ship[0]
-          if ship and ship > 0
+          if ship? and ship > 0
             secretary = _ships[ship].api_ship_id
             @setState
               fleetSecretary: secretary
             if @state.notifySecretary == 0
               @updateNotifyConfig(secretary)

-    ###*
-     * Update notification config using audio of ship
-     * @param {number} ship id in window.$ships or default audio if equals -1
-     ###
     updateNotifyConfig: (ship_id) ->
-      if ship_id == -1
-        for s in ['construction','repair','expedition','morale']
-          config.set("poi.notify.#{s}.audio")
-      if ship_id > 0
-        server = SERVERS[(ship_id + SERVER_NONCE) % SERVERS.length]
-        filename = @state.shipgraph[ship_id]?.api_filename
-        return unless server
-        return unless filename
-        audio_constr = "http://#{server}/kcs/sound/kc#{filename}/5.mp3"
-        audio_expedi = "http://#{server}/kcs/sound/kc#{filename}/7.mp3"
-        audio_repair = "http://#{server}/kcs/sound/kc#{filename}/6.mp3"
-        audio_morale = "http://#{server}/kcs/sound/kc#{filename}/27.mp3"
-        config.set('poi.notify.construction.audio', audio_constr)
-        config.set('poi.notify.expedition.audio', audio_expedi)
-        config.set('poi.notify.repair.audio', audio_repair)
-        config.set('poi.notify.morale.audio', audio_morale)
+      return unless ship_id > 0
+      server = SERVERS[(ship_id + SERVER_NONCE) % SERVERS.length]
+      filename = @state.shipgraph[ship_id]?.api_filename
+      return unless server
+      return unless filename
+      audio_constr = "http://#{server}/kcs/sound/kc#{filename}/5.mp3"
+      audio_expedi = "http://#{server}/kcs/sound/kc#{filename}/7.mp3"
+      audio_repair = "http://#{server}/kcs/sound/kc#{filename}/6.mp3"
+      audio_morale = "http://#{server}/kcs/sound/kc#{filename}/27.mp3"
+      config.set('poi.notify.construction.audio', audio_constr)
+      config.set('poi.notify.expedition.audio', audio_expedi)
+      config.set('poi.notify.repair.audio', audio_repair)
+      config.set('poi.notify.morale.audio', audio_morale)

     handleShipChange: (e) ->
       ship_id = parseInt(e.target.value)
       return if ship_id is NaN
       # Save secretary config
+      config.set('plugin.secretary.ship', ship_id)
       @setState
         notifySecretary: ship_id
-      config.set('plugin.secretary.ship', ship_id)
       # Update notify config
       if ship_id == 0
         @updateNotifyConfig(@state.fleetSecretary)
@@ -132,26 +127,23 @@
     handleAudition: (type) ->
       switch type
         when 'construction'
-          notify "Construction notification",
+          notify null,
             type: 'construction'
         when 'expedition'
-          notify "Expedition notification",
+          notify null,
             type: 'expedition'
         when 'repair'
-          notify "Repair notification",
+          notify null,
             type: 'repair'
         when 'morale'
-          notify "Morale notification",
+          notify null,
             type: 'morale'

-    handleRefresh: () ->
-      # Reset server nonce
-      SERVER_NONCE = Math.floor(Math.random() * SERVERS.length)
-      # Update notify config
-      if @state.notifySecretary == 0
-        @updateNotifyConfig(@state.fleetSecretary)
-      else
-        @updateNotifyConfig(@state.notifySecretary)
+    handleDisable: ->
+      for s in ['construction', 'repair', 'expedition', 'morale']
+        config.set("poi.notify.#{s}.audio")
+      @setState
+        notifySecretary: -1

     render: ->

@@ -166,14 +158,20 @@
           {
             if @state.isLogin
               options = []
-              options.push(
-                <option key={-1} value={-1}>
-                  {__ 'Default poi: Reset to default audio poi'}
-                </option>
-              )
+              if @state.notifySecretary == -1
+                options.push(
+                  <option key={-1} value={-1}>
+                    {__ 'Disabled'}
+                  </option>
+                )
               options.push(
                 <option key={0} value={0}>
-                  {__ 'Current secretary'}: { if @state.fleetSecretary && window.$ships[@state.fleetSecretary]? then window.i18n.resources.__ window.$ships[@state.fleetSecretary].api_name else __ 'Unknown' }
+                  {__ 'Current secretary'}: {
+                    if $ships[@state.fleetSecretary]?
+                      __r $ships[@state.fleetSecretary].api_name
+                    else
+                      __ 'Unknown'
+                  }
                 </option>
               )
               for ship, i in @state.ships
@@ -213,6 +211,11 @@

         <Grid>
+          <Col xs=6>
+            <Button bsStyle='warning' style={width: '100%'} onClick={@handleDisable}>
+              {__ 'Disable'}
+            </Button>
+          </Col>
         </Grid>


Fix bug

Fix bug

--- index.cjsx
+++ index.cjsx
@@ -151,8 +151,8 @@

         when '/kcsapi/api_req_battle_midnight/battle'
           flag = true
-          nowFriendHp = afterFriendHp
-          nowEnemyHp = afterEnemyHp
+          nowFriendHp = Object.clone afterFriendHp
+          nowEnemyHp = Object.clone afterEnemyHp
           if body.api_hougeki?
             hougeki = body.api_hougeki
             for damageFrom, i in hougeki.api_at_list


Small tweak of player info

Small tweak of player info

--- static/script/games/settlers/settlers.coffee
+++ static/script/games/settlers/settlers.coffee
@@ -344,18 +344,15 @@

-
{{points}} {{#_g}}points{{/_g}}

+
{{#_g}}Points{{/_g}}:	{{points}}

       {{#my_player}}

{{#_g}}Wood{{/_g}}:	{{resources.wood}}

{{#_g}}Clay{{/_g}}:	{{resources.clay}}

{{#_g}}Sheep{{/_g}}:	{{resources.sheep}}

{{#_g}}Grain{{/_g}}:	{{resources.grain}}

{{#_g}}Rock{{/_g}}:	{{resources.rock}}

-
{{#_g}}Total{{/_g}}:	{{resources.total}}

       {{/my_player}}
-      {{^my_player}}
-
{{#_g}}Resources{{/_g}}:	{{resources.total}}

-      {{/my_player}}
+
{{#_g}}Resources{{/_g}}:	{{resources.total}}

{{#_g}}Cards{{/_g}}:	{{cards.unused_cards}}

{{#_g}}Knights{{/_g}}:	{{cards.used_knights}}


remove old less require spec

remove old less require spec

--- spec/stdlib/require-spec.coffee
+++ spec/stdlib/require-spec.coffee
@@ -1,19 +0,0 @@
-{less} = require('less')
-
-describe "require", ->
-  describe "files with a `.less` extension", ->
-    it "parses valid files into css", ->
-      output = require(project.resolve("sample.less"))
-      expect(output).toBe """
-        #header {
-          color: #4d926f;
-        }
-        h2 {
-          color: #4d926f;
-        }
-
-      """
-
-    it "throws an error when parsing invalid file", ->
-      functionWithError = (-> require(project.resolve("sample-with-error.less")))
-      expect(functionWithError).toThrow()


set oridomiSupport to false if style check fails

--- oriDomi.coffee
+++ oriDomi.coffee
@@ -72,6 +72,7 @@
   css[key] = testProp value
   if !css[key]
     console?.warn 'oriDomi: Browser does not support oriDomi'
+    oridomiSupport = false
     break

 css.gradientProp = getGradientProp()
@@ -98,6 +99,8 @@

   constructor: (@el, @settings = {}) ->
     console.time 'oridomiConstruction'
+    return @el if !oridomiSupport
+
     if !(@ instanceof OriDomi)
       return new oriDomi @el, @settings


map method

map method

--- oridomi.coffee
+++ oridomi.coffee
@@ -1124,6 +1124,19 @@
   unfold: prep (callback) -> @_unfold arguments...

+  # For custom folding behavior, you can pass a function to map that will
+  # determine the folding angle applied to each panel. The passed function
+  # is supplied with the input angle, the panel index, and the number of
+  # panels in the active anchor. Calling map returns a new function bound to
+  # the instance and the lambda, e.g. `oridomi.map(randomFn)(30).reveal(20)`.
+  map: (fn) ->
+    prep((angle, anchor, options) =>
+      @_iterate anchor, (panel, i, len) =>
+        deg = fn angle, i, len
+        @_transformPanel panel, deg, anchor, options.fracture
+    ).bind @
+
+
   # Convenience Methods
   # ===================


set $ to null in absence of dom lib

set $ to null in absence of dom lib

--- oridomi.coffee
+++ oridomi.coffee
@@ -12,9 +12,9 @@
 'use strict'

 # Set a reference to jQuery (or another `$`-aliased DOM library).
-# If it doesn't exist, set to false so oriDomi knows we are working without jQuery.
+# If it doesn't exist, set to null so oriDomi knows we are working without jQuery.
 # oriDomi doesn't require it to work, but offers a useful plugin bridge.
-$ = if window.$?.data then window.$ else false
+$ = if window.$?.data then window.$ else null

 # `devMode` determines whether oriDomi is vocal in the console with warnings and benchmarks.
 # Turn it on externally by calling `OriDomi.devMode()`.


better calculation for mobile detection

better calculation for mobile detection

--- lib/mixins/mobile-detection.coffee
+++ lib/mixins/mobile-detection.coffee
@@ -2,7 +2,11 @@

 mobileDetection = Em.Mixin.create
   isMobileDevice: (->
-    return window.innerWidth <= 800 && window.innerHeight <= 600
+    devicePixelRatio = window.devicePixelRatio || 1
+    width = window.innerWidth / devicePixelRatio
+    height = window.innerHeight / devicePixelRatio
+
+    return width <= 800 && height <= 600
   ).property()

 `export default mobileDetection`


Revert to the placeholder option simply being "placeholder"

--- app/assets/javascripts/exo/views/token_field.js.coffee
+++ app/assets/javascripts/exo/views/token_field.js.coffee
@@ -48,10 +48,8 @@
         @$originalInput = $(@originalInput)
         @$originalInput.wrap(@el)
         @$originalInput.attr("type", "hidden")
-        @placeholderText ||= @$originalInput.attr("placeholder")
+        @placeholder ||= @$originalInput.attr("placeholder")
         @setElement @$originalInput.parents()[0]
-
-      @placeholderText ||= ""

       @selectedIndex = TokenField.TokenIndexes.Input
       @_collection = new Thorax.Collection


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/approachable.coffee
+++ test/approachable.coffee
@@ -40,6 +40,29 @@

   equals obj.I.cooldowns.powerup.value, 0, "powerup should not cooldown past target value"

+test "adding many cooldowns to defaults", ->
+  obj = GameObject
+    cooldowns:
+      cool:
+        approachBy: 5
+        target: -5
+        value: 20
+      rad:
+        approachBy: 0.5
+        target: 2
+        value: 0
+      tubular:
+        approachBy: 1
+        target: 1000
+        value: 0
+
+  4.times ->
+    obj.update()
+
+  equals obj.I.cooldowns.cool.value, 0
+  equals obj.I.cooldowns.rad.value, 0
+  equals obj.I.cooldowns.tubular.value, 4
+
 test "#addCooldown", 3, ->
   obj = GameObject()


Fix selecting in Safari

Fix selecting in Safari

--- vendor/assets/javascripts/chosen/parser.js.coffee
+++ vendor/assets/javascripts/chosen/parser.js.coffee
@@ -167,7 +167,7 @@
   select: (option) ->
     return @ unless option

-    option.$option.attr(selected: "selected")[0].selected = true
+    option.$option.prop(selected: true)
     option.$listed.addClass("selected")
     option.$choice.addClass("selected")
     option.selected = true
@@ -181,7 +181,7 @@
   deselect: (option) ->
     return @ unless option

-    option.$option.removeAttr("selected")[0].selected = false
+    option.$option.prop(selected: false)
     option.$listed.removeClass("selected")
     option.$choice.removeClass("selected")
     option.selected = false


Remove css banner; isn't served anyhow

Remove css banner; isn't served anyhow

--- js-library/Gruntfile.coffee
+++ js-library/Gruntfile.coffee
@@ -71,8 +71,6 @@
           'build/css/framed_controls.css': ['build/css/framed_controls_without_embeds.css']
     cssmin:
       build:
-        options:
-          banner: banner_template
         expand: true
         cwd: 'build/css/'
         dest: 'build/css/'


Boyscout - remove extra enter

Boyscout - remove extra enter

--- js-library/Gruntfile.coffee
+++ js-library/Gruntfile.coffee
@@ -31,7 +31,6 @@
         src: [
           'build/js/libs/jquery-1.7.2.js'
           'build/js/core.js'
-
           'build/js/wrap/first.js'
           'build/js/plugins/*.js'
           'build/js/classes/*.js'


annular wedge now works

annular wedge now works

--- bokehjs/src/coffee/renderer/glyph/annular_wedge.coffee
+++ bokehjs/src/coffee/renderer/glyph/annular_wedge.coffee
@@ -66,50 +66,6 @@
         if @do_stroke
           glyph_props.line_properties.set_vectorize(ctx, i)
           ctx.stroke()
-
-    _fast_path: (ctx) ->
-      if @do_fill
-        @glyph_props.fill_properties.set(ctx, @glyph_props)
-        for i in [0..@sx.length-1]
-          if isNaN(@sx[i] + @sy[i] + @inner_radius[i] + @outer_radius[i] + @start_angle[i] + @end_angle[i])
-            continue
-
-          ctx.translate(@sx[i], @sy[i])
-          ctx.rotate(@start_angle[i])
-
-          ctx.moveTo(@outer_radius[i], 0)
-          ctx.beginPath()
-          ctx.arc(0, 0, @outer_radius[i], 0, @angle[i], @direction[i])
-          ctx.rotate(@angle[i])
-          ctx.lineTo(@inner_radius[i], 0)
-          ctx.arc(0, 0, @inner_radius[i], 0, -@angle[i], not @direction[i])
-          ctx.closePath()
-          ctx.fill()
-
-          ctx.rotate(-@angle[i]-@start_angle[i])
-          ctx.translate(-@sx[i], -@sy[i])
-
-      if @do_stroke
-        @glyph_props.line_properties.set(ctx, @glyph_props)
-        ctx.beginPath()
-        for i in [0..@sx.length-1]
-          if isNaN(@sx[i] + @sy[i] + @inner_radius[i] + @outer_radius[i] + @start_angle[i] + @end_angle[i])
-            continue
-
-          ctx.translate(@sx[i], @sy[i])
-          ctx.rotate(@start_angle[i])
-
-          ctx.moveTo(@outer_radius[i], 0)
-          ctx.arc(0, 0, @outer_radius[i], 0, @angle[i], @direction[i])
-          ctx.rotate(@angle[i])
-          ctx.lineTo(@inner_radius[i], 0)
-          ctx.arc(0, 0, @inner_radius[i], 0, -@angle[i], not @direction[i])
-          ctx.closePath()
-
-          ctx.rotate(-@angle[i]-@start_angle[i])
-          ctx.translate(-@sx[i], -@sy[i])
-
-        ctx.stroke()

     draw_legend: (ctx, x1, x2, y1, y2) ->


Revert "fix function parameter parsing"

Revert "fix function parameter parsing"

woops; only seemed to fix it because it removed the other case

This reverts commit 1e4c58eaf2287fe156f41dc69d578a0b3559728c.

--- grammar.coffee
+++ grammar.coffee
@@ -198,10 +198,8 @@
     signature: -> seq(@parameters, optional(@result))
     result: -> choice(@parameters, @type)
     parameters: -> seq("(", optional(seq(@parameter_list, optional(","))), ")")
-    parameter_list: -> choice(
-      seq(@nameless_parameter_decl, repeat(seq(",", @nameless_parameter_decl))))
-    named_parameter_decl: -> seq(@identifier_list, optional("..."), @type)
-    nameless_parameter_decl: -> seq(optional("..."), @type)
+    parameter_list: -> seq(@parameter_decl, repeat(seq(",", @parameter_decl)))
+    parameter_decl: -> seq(optional(@identifier_list), optional("..."), @type)

     interface_type: -> seq(keyword("interface"), "{", repeat(seq(@method_spec, terminator())), "}")
     method_spec: -> choice(seq(@method_name, @signature), @interface_type_name)


groupCustomization : permission check before uploading photo.

--- client/Social/Activity/views/feedcoverphotoview.coffee
+++ client/Social/Activity/views/feedcoverphotoview.coffee
@@ -20,8 +20,7 @@
     @addSubView container

   getCoverUpdateButton: ->
-    admin = role for role in KD.config.roles when role is "admin"
-    if admin
+    if "admin" in KD.config.roles
       new KDButtonView
         style  : "solid green small account-header-button"
         type   : "submit"


StacksCustomViews: re-order function definitions for easy to navigate

--- client/admin/lib/views/stacks/stackscustomviews.coffee
+++ client/admin/lib/views/stacks/stackscustomviews.coffee
@@ -443,6 +443,119 @@
         fields: input: {name, label, defaultValue: value}

+    repoList: (options) =>
+
+      controller    = new kd.ListViewController
+        viewOptions :
+          itemClass : StackRepoUserItem
+          cssClass  : 'repo-user-list'
+
+      __view = controller.getListView()
+      return { __view, controller }
+
+
+    repoListView: (options) =>
+
+      container    = @views.container 'repo-listview'
+      loader       = @addTo container,
+        mainLoader : 'Fetching repositories list...'
+
+      fetchGithubRepos options, (err, repo_data) =>
+
+        showError err
+
+        loader.hide()
+
+        views        = @addTo container,
+          text       : "Github: Select a repository from your account"
+          repoList   : options
+
+        {controller, __view: repoList} = views.repoList
+
+        {orgs, users} = repo_data
+        controller.replaceAllItems users.concat orgs
+
+        container.forwardEvent repoList, 'RepoSelected'
+
+      return container
+
+
+    credentialList: (options) =>
+
+      { provider, stackTemplate } = options
+
+      listView   = new AccountCredentialList
+        itemClass   : CredentialListItem
+        itemOptions : { stackTemplate }
+
+      controller = new AccountCredentialListController
+        view        : listView
+        wrapper     : no
+        scrollView  : no
+        provider    : provider
+
+      __view = controller.getView()
+      return { __view, controller }
+
+
+    providersView: (options) =>
+
+      {providers, enabled} = options
+      enabled  ?= providers
+
+      container = @views.container 'providers'
+
+      providers.forEach (provider) =>
+
+        return  if provider in ['custom', 'managed']
+
+        name = globals.config.providers[provider]?.name or provider
+
+        @addTo container,
+          button     :
+            title    : name
+            cssClass : provider
+            disabled : provider not in enabled
+            callback : ->
+              container.emit 'ItemSelected', provider
+
+      return container
+
+
+    stepsHeader: (options) =>
+
+      { title, index, selected } = options
+
+      container = @views.container "#{if selected then 'selected' else ''}"
+
+      @addTo container,
+        text_step  : index
+        text_title : title
+
+      return container
+
+
+    stepsHeaderView: (options) =>
+
+      { steps, selected } = options
+
+      container = @views.container 'steps-view'
+
+      @addTo container, view :
+        cssClass : 'vline'
+        tagName  : 'cite'
+
+      steps = steps.slice 0
+      steps.forEach (step, index) =>
+
+        step.index    = index + 1
+        step.selected = selected? and selected is step.index
+
+        @addTo container, stepsHeader: step
+
+      return container
+
+
     initialView: (callback) =>

       container = @views.container 'stacktemplates'
@@ -484,6 +597,7 @@
       __view = controller.getView()
       return { __view, controller }

+    # STEPS --------------------------------------------------------------------

     stepSelectRepo: (options) =>

@@ -627,61 +741,6 @@
         callback data

       return container
-
-
-    repoList: (options) =>
-
-      controller    = new kd.ListViewController
-        viewOptions :
-          itemClass : StackRepoUserItem
-          cssClass  : 'repo-user-list'
-
-      __view = controller.getListView()
-      return { __view, controller }
-
-
-    repoListView: (options) =>
-
-      container    = @views.container 'repo-listview'
-      loader       = @addTo container,
-        mainLoader : 'Fetching repositories list...'
-
-      fetchGithubRepos options, (err, repo_data) =>
-
-        showError err
-
-        loader.hide()
-
-        views        = @addTo container,
-          text       : "Github: Select a repository from your account"
-          repoList   : options
-
-        {controller, __view: repoList} = views.repoList
-
-        {orgs, users} = repo_data
-        controller.replaceAllItems users.concat orgs
-
-        container.forwardEvent repoList, 'RepoSelected'
-
-      return container
-
-
-    credentialList: (options) =>
-
-      { provider, stackTemplate } = options
-
-      listView   = new AccountCredentialList
-        itemClass   : CredentialListItem
-        itemOptions : { stackTemplate }
-
-      controller = new AccountCredentialListController
-        view        : listView
-        wrapper     : no
-        scrollView  : no
-        provider    : provider
-
-      __view = controller.getView()
-      return { __view, controller }

     stepSetupCredentials: (options) =>
@@ -864,61 +923,3 @@
                   callback stackTemplate

       return container
-
-
-    providersView: (options) =>
-
-      {providers, enabled} = options
-      enabled  ?= providers
-
-      container = @views.container 'providers'
-
-      providers.forEach (provider) =>
-
-        return  if provider in ['custom', 'managed']
-
-        name = globals.config.providers[provider]?.name or provider
-
-        @addTo container,
-          button     :
-            title    : name
-            cssClass : provider
-            disabled : provider not in enabled
-            callback : ->
-              container.emit 'ItemSelected', provider
-
-      return container
-
-
-    stepsHeader: (options) =>
-
-      { title, index, selected } = options
-
-      container = @views.container "#{if selected then 'selected' else ''}"
-
-      @addTo container,
-        text_step  : index
-        text_title : title
-
-      return container
-
-
-    stepsHeaderView: (options) =>
-
-      { steps, selected } = options
-
-      container = @views.container 'steps-view'
-
-      @addTo container, view :
-        cssClass : 'vline'
-        tagName  : 'cite'
-
-      steps = steps.slice 0
-      steps.forEach (step, index) =>
-
-        step.index    = index + 1
-        step.selected = selected? and selected is step.index
-
-        @addTo container, stepsHeader: step
-
-      return container


Client: app: activitysidebar: remove unused sinkrow import

--- client/app/lib/activity/sidebar/activitysidebar.coffee
+++ client/app/lib/activity/sidebar/activitysidebar.coffee
@@ -4,7 +4,6 @@
 remote                          = require('../../remote').getInstance()
 globals                         = require 'globals'
 Promise                         = require 'bluebird'
-sinkrow                         = require 'sinkrow'
 Machine                         = require 'app/providers/machine'
 FSHelper                        = require '../../util/fs/fshelper'
 isKoding                        = require 'app/util/isKoding'


sidebar: wait for maincontroller to be ready for updateMachines

--- client/app/lib/activity/sidebar/activitysidebar.coffee
+++ client/app/lib/activity/sidebar/activitysidebar.coffee
@@ -640,7 +640,8 @@

   updateMachines: (callback = kd.noop) ->

-    @fetchEnvironmentData @bound 'redrawMachineList'
+    kd.singletons.mainController.ready =>
+      @fetchEnvironmentData @bound 'redrawMachineList'

   invalidateWorkspaces: (machine) ->


Bugfix geyser KO binding handler for empty nodes. PP-10

--- client/src/scripts/knockout.coffee
+++ client/src/scripts/knockout.coffee
@@ -19,7 +19,8 @@

 ko.bindingHandlers.geyser =
   update: (element, valueAccessor, allBindings, viewModel, bindingContext) ->
-    $(element).html geyser.render ko.unwrap valueAccessor()
+    if data = ko.unwrap valueAccessor()
+      $(element).html geyser.render data


stacks: no stacks sidebar message copy updated

--- client/app/lib/activity/sidebar/activitysidebar.coffee
+++ client/app/lib/activity/sidebar/activitysidebar.coffee
@@ -522,8 +522,9 @@

     @stacksNotConfiguredWarning = @machinesWrapper.addSubView (new KDCustomHTMLView
       cssClass : 'stack-warning hidden'
-      partial  : "Compute Stacks has not been configured yet for this Team.

-                  click here to setup now."
+      partial  : "Your team page has not been

+                  fully configured yet please

+                  click here to setup now."
       ), null, shouldPrepend = yes

     @showStacksNotConfiguredWarning()


Create a individual method to fetch only one app metadata at once

--- client/landing/app/MainApp/kodingappscontroller.coffee
+++ client/landing/app/MainApp/kodingappscontroller.coffee
@@ -67,6 +67,35 @@
         else
           callback? err, apps

+  fetchAppFromFs:(appName, cb)->
+
+    manifests = @constructor.manifests
+    appsPath = "/home/#{KD.nick()}/Applications/"
+    suffix   = ".kdapp/manifest.json"
+
+    manifest = FSHelper.createFileFromPath "#{appsPath}#{appName}#{suffix}"
+    manifest.fetchContents (err, response)=>
+      warn err  if err
+      return cb null  if err
+      return cb null  unless response
+
+      try
+        manifest = JSON.parse response
+        manifests["#{manifest.name}"] = manifest
+      catch e
+        warn "Manifest file is broken:", e
+        return cb null
+
+      @putAppsToAppStorage manifests
+      cb null
+
+  fetchIndividualAppsFromFS:(apps, callback)->
+
+    stack = []
+    apps.forEach (app)=>
+      stack.push (cb)=> @fetchAppFromFs app, cb
+    async.parallel stack, callback
+
   fetchAppsFromFs:(cb)->

     @_fetchQueue.push cb  if cb
@@ -81,32 +110,10 @@
         callback()  for callback in @_fetchQueue
         @_fetchQueue = []
       else
-        apps  = []
-        stack = []
-
-        files.forEach (file)->
-          if /\.kdapp$/.test(file.name) and file.type is 'folder'
-            apps.push file
-
-        apps.forEach (app)=>
-          stack.push (cb)=>
-            manifest = FSHelper.createFileFromPath "#{app.path}/manifest.json"
-            manifest.fetchContents (err, response)->
-              # shadowing the error is intentional here
-              # to not to break the result of the stack
-              cb null, response
-
-        manifests = @constructor.manifests
-        async.parallel stack, (err, result)=>
-          result.forEach (rawManifest)->
-            if rawManifest
-              try
-                manifest = JSON.parse rawManifest
-                manifests["#{manifest.name}"] = manifest
-              catch e
-                console.warn "Manifest file is broken", e
-          @putAppsToAppStorage manifests
-          callback null, manifests  for callback in @_fetchQueue
+        apps = @filterAppsFromFileList files
+        @fetchIndividualAppsFromFS apps, (result)=>
+          for callback in @_fetchQueue
+            callback null, @constructor.manifests
           @_fetchQueue = []
     , ->
       warn msg = "Timeout reached for kite request"
@@ -175,7 +182,9 @@
     @appStorage.reset()
     @appStorage.setValue 'shortcuts', defaultShortcuts, callback

-  putAppsToAppStorage:(apps)->
+  putAppsToAppStorage:(apps, callback = noop)->
+    apps or= @constructor.manifests
+    @appStorage.setValue 'apps', apps, callback

     @appStorage.setValue 'apps', apps


Fix up some removal semantics

Fix up some removal semantics

Objects were showing up twice in the ToC

--- scripts/mixins/tree.coffee
+++ scripts/mixins/tree.coffee
@@ -30,7 +30,7 @@
         #

         # Remove the child if it is already attached somewhere
-        child._tree_parent.removeChild(child) if child._tree_parent
+        child._tree_parent.removeChild(child) if child._tree_parent and child._tree_parent != @

         child._tree_parent = @
         child._tree_root = @_tree_root
@@ -57,12 +57,15 @@

     newNode: (options) -> throw 'BUG: Only the root can create new Pointer Nodes'

-    getParent: () -> @_tree_parent
+    getParent:   () -> @_tree_parent
     getChildren: () -> @_tree_children

     removeChild: (model) ->
-      throw 'BUG: child is not in this node' if not @getChildren().get(model.id)
-      @getChildren().remove(model.id)
+      children = @getChildren()
+
+      throw 'BUG: child is not in this node' if not (children.contains(model) or children.get(model.id))
+      model = children.get(model.id) if !children.contains(model)
+      children.remove(model)

     addChild: (model, at=0) ->
       children = @getChildren()
@@ -77,6 +80,7 @@
         if children.indexOf(realModel) < at
           at = at - 1
         children.remove(realModel)
+        realModel._tree_parent = null

       # Before adding the model make sure it's a Tree Node (does it have `._children`.
       # If not, wrap it in a node


Start timer on robot entrance

Start timer on robot entrance

--- scripts/poo-tracker.coffee
+++ scripts/poo-tracker.coffee
@@ -4,6 +4,8 @@
 info = Url.parse process.env.POO_REDIS_URL or "redis://localhost:6379/0"
 redis_client = Redis.createClient(info.port, info.hostname)
 redis_client.auth info.auth.split(":")[1] if info.auth
+
+check_interval = null

 module.exports = (robot) ->
   POO_TRACKER_KEY = "poops"
@@ -28,6 +30,6 @@
       return if reply == null
       return res.send "Latest poo: #{reply}"

-  robot.adapter.on 'connected', ->
-    console.log("Connected event for poop tracker")
-    setInterval checkRedisForShit, 1000
+  robot.enter (msg) ->
+    if msg.message.user.name == robot.name and !check_interval
+      check_interval = setInterval checkRedisForShit, 1000


100 seems like a saner default

100 seems like a saner default

--- app/scripts/hello.coffee
+++ app/scripts/hello.coffee
@@ -5,7 +5,7 @@
 	$scope.pad = ""
 	pad = $scope.pad
 	stats = {}
-	$scope.readability = 999
+	$scope.readability = 100
 	$scope.check = (e) ->
     # Only recalculate when hitting the 'space' or 'return' keys
     if e.keyCode in [32, 13]


no need to send cache: true

no need to send cache: true

git-svn-id: 00f01ee4f729ccc52c3e336777c3e81b5adc88db@249619 ef392f78-ee14-0410-84e5-9db46baae5bb

--- app/scripts/model.coffee
+++ app/scripts/model.coffee
@@ -125,7 +125,6 @@
             defaultcontext: settings.defaultOverviewContext
             show: []
             show_struct: []
-            cache : true

         $.extend data, kwicResults.getPageInterval(page), o.ajaxParams
         for corpus in settings.corpusListing.selected
@@ -179,7 +178,6 @@
             corpus: settings.corpusListing.stringifySelected()
             incremental: $.support.ajaxProgress
             type: type
-            cache : true
             max : 1000
         @prevParams = params
         def =  $.ajax


trying to fix auto-import test

trying to fix auto-import test

--- tests/poller.coffee
+++ tests/poller.coffee
@@ -1,5 +1,6 @@
 should = require 'should'
 sinon = require 'sinon'
+moment = require 'moment'
 Konnector = require '../server/models/konnector'
 poller = require '../server/lib/konnector_poller'

@@ -21,6 +22,7 @@
             data =
                 isImporting: false
                 importInterval: 'week'
+                lastAutoImport: moment().format()
                 slug: 'test'
             poller.create(data)
             @spy.callCount.should.equal 1


use dir of versions not version

use dir of versions not version

--- server-ce/Gruntfile.coffee
+++ server-ce/Gruntfile.coffee
@@ -27,7 +27,7 @@
                                         #        access_token: ''
                                         concat: true
                                 src: repos
-                                dest: 'version/' + tag + '.json'
+                                dest: 'versions/' + tag + '.json'

         grunt.loadNpmTasks 'grunt-docker-io'
         grunt.loadNpmTasks 'grunt-github-api'


Update setupApi.coffee

Update setupApi.coffee

--- server/api/setupApi.coffee
+++ server/api/setupApi.coffee
@@ -417,7 +417,7 @@
             {
                 checkins: await Checkin.findBySession(i)
                 max: MAX_CHECKIN_PER_SESSION[i]
-            } for i in [0..2])
+            } for i in [0..1])
         for sessionCheckins in checkins
             sessionCheckins.checkins = await awaitAll(sessionCheckins.checkins.map((checkin) ->
                 checkin = checkin.toObject()


ComputeController: errorHandler: request verification on NotVerified error

--- client/Main/providers/computecontroller.coffee
+++ client/Main/providers/computecontroller.coffee
@@ -216,6 +216,8 @@
           if err.code is Error.Pending
             retried = retryIfNeeded task, machine
             safeToSuspend = yes
+          else if err.code is Error.NotVerified
+            ComputeController.UI.requestVerify()
           else
             warn "[CC] error:", err


fix: content displays destroyed when a develop tab is clicked

--- client/app/MainApp/maintabs/maintabview.coffee
+++ client/app/MainApp/maintabs/maintabview.coffee
@@ -26,6 +26,7 @@
       @removePane pane
       return no

+    @getSingleton("contentDisplayController").emit "ContentDisplaysShouldBeHidden"
     @showPane pane

   showHandleContainer:()->


Client: Sidebar: Using `bound` instead of fat arrow

--- client/app/lib/components/sidebar/index.coffee
+++ client/app/lib/components/sidebar/index.coffee
@@ -41,7 +41,7 @@
   componentWillMount: ->

     EnvironmentFlux.actions.loadStacks()
-    EnvironmentFlux.actions.loadMachines().then => @setActiveInvitationMachineId()
+    EnvironmentFlux.actions.loadMachines().then @bound 'setActiveInvitationMachineId'
     actions.channel.loadFollowedPublicChannels()
     actions.channel.loadFollowedPrivateChannels()


minor: coffee style interpolation

minor: coffee style interpolation

--- client/app/lib/components/tooltip/index.coffee
+++ client/app/lib/components/tooltip/index.coffee
@@ -16,9 +16,9 @@
   componentDidMount: ->

     TooltipWrapperDOMNode                  = @refs.TooltipWrapper.getDOMNode()
-    TooltipWrapperDOMNode.style.top        = @props.tooltipY + "px"
-    TooltipWrapperDOMNode.style.left       = @props.tooltipX + "px"
-    TooltipWrapperDOMNode.style.marginLeft = "#{ -TooltipWrapperDOMNode.offsetWidth / 2 }px"
+    TooltipWrapperDOMNode.style.top        = "#{@props.tooltipY}px"
+    TooltipWrapperDOMNode.style.left       = "#{@props.tooltipX}px"
+    TooltipWrapperDOMNode.style.marginLeft = "#{-TooltipWrapperDOMNode.offsetWidth / 2}px"

   render: ->


flux/env: use machine from cc storage, don't revive

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

--- client/app/lib/flux/environment/actions.coffee
+++ client/app/lib/flux/environment/actions.coffee
@@ -199,7 +199,7 @@

   debug 'acceptInvitation invitation', invitation

-  machine = remote.revive _machine.toJS()
+  machine = computeController.storage.machines.get '_id', _machine.get '_id'

   debug 'acceptInvitation machine', machine


AppsWatcher extended from FSWatcher

AppsWatcher extended from FSWatcher

--- client/landing/app/MainApp/fs/fswatcher.coffee
+++ client/landing/app/MainApp/fs/fswatcher.coffee
@@ -72,3 +72,56 @@
     FSWatcher.stopWatching @getFullPath()

   getFullPath:-> "[#{@vmName}]#{@path}"
+
+
+class AppsWatcher extends FSWatcher
+
+  # Emits following signals:
+  #
+  #  - ANewAppAdded
+  #  - AFileRemoved
+  #  - AnAppRemoved
+  #  - AFileChanged
+  #  - AManifestChanged
+  #
+  # All includes (app, change) as parameter
+
+  constructor:(options={})->
+    options.path = "~/Applications"
+    super options
+    @_trackedApps = []
+
+  onFolderAdded:(change)->
+    if @isKdApp change
+      app = @getAppName change
+      return  if app in @_trackedApps
+      @_trackedApps.push app
+      log "A new app added:", app
+      @emit "ANewAppAdded", app, change
+
+  onFileRemoved:(change)->
+    app = @getAppName change
+    if (@isKdApp change) or (@isManifest change)
+      @_trackedApps = (_app for _app in @_trackedApps when _app isnt app)
+      log "An app removed:", app
+      @emit "AnAppRemoved", app, change
+    else if @isInKdApp change
+      log "A file '#{change.file.name}' removed from #{app} app."
+      @emit "AFileRemoved", app, change
+
+  onFileAdded:(change)->
+    app = @getAppName change
+    if @isInKdApp change
+      if @isManifest change
+        log "A manifest changed/added:", @getAppName change
+        @emit "AManifestChanged", app, change
+      else
+        # log "A sourcefile changed/added:", @getAppName change
+        @emit "AFileChanged", app, change
+
+  # Helpers
+  isKdApp   :(change)-> /\.kdapp$/.test change.file.fullPath
+  isInKdApp :(change)-> /Applications\/.*\.kdapp/.test change.file.fullPath
+  isManifest:(change)-> /manifest\.json$/.test change.file.fullPath
+  getAppName:(change)->
+    (change.file.fullPath.match /Applications\/([^\/]+)\.kdapp/)[1]


Improve Accounts.onCreateUser to get name and email from any service

--- server/lib/accounts.coffee
+++ server/lib/accounts.coffee
@@ -26,35 +26,23 @@
 	unless Meteor.users.findOne()
 		user.admin = true

-	serviceName = null
+	if not user?.name? or user.name is ''
+		if options.profile?.name?
+			user.name = options.profile?.name

-	if user.services?.facebook?
-		serviceName = 'facebook'
-	else if user.services?.google?
-		serviceName = 'google'
-	else if user.services?.github?
-		serviceName = 'github'
-	else if user.services?.gitlab?
-		serviceName = 'gitlab'
-	else if user.services?['meteor-developer']?
-		serviceName = 'meteor-developer'
-	else if user.services?.twitter?
-		serviceName = 'twitter'
+	if user.services?
+		for serviceName, service of user.services
+			if not user?.name? or user.name is ''
+				if service.name?
+					user.name = service.name
+				else if service.username?
+					user.name = service.username

-	if serviceName in ['facebook', 'google', 'meteor-developer', 'github', 'gitlab', 'twitter']
-		if not user?.name? or user.name is ''
-			if options.profile?.name?
-				user.name = options.profile?.name
-			else if user.services[serviceName].name?
-				user.name = user.services[serviceName].name
-			else
-				user.name = user.services[serviceName].username
-
-		if user.services[serviceName].email
-			user.emails = [
-				address: user.services[serviceName].email
-				verified: true
-			]
+			if not user.emails? and service.email?
+				user.emails = [
+					address: service.email
+					verified: true
+				]

 	return user


Log downloaded values

Log downloaded values

--- server/lib/download.coffee
+++ server/lib/download.coffee
@@ -19,6 +19,8 @@
                 gzip: true,
                 timeout: options?.timeout || 7 * 1000
             })
+            extract = page.substring(0, 50)
+            logger.info "Downloaded #{href} -> `#{extract}...`"
             return page
         catch e
             logger.info "Error downloading " + href + " " + i + " will re-download"


Fix trailing space

Fix trailing space

--- server/models/event.coffee
+++ server/models/event.coffee
@@ -68,7 +68,7 @@
         date = momentTz(@start)

     date.tz User.timezone
-    dStr = date.format dateFormat
+    dStr = date.format dateFormat
     dStr += ' ' + User.timezone

     return dStr


fix typos in build

fix typos in build

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -32,7 +32,7 @@
 # high level tasks
 # =================================================================
 gulp.task "build", (cb)->
-  return runSequence  ['clean:build', 'css:clean', 'js:clean'],
+  return runSequence  ['build:clean', 'css:clean', 'js:clean'],
                       ['css:build:all','js:build:all'],
                       'build_wintersmith', cb


Updated map

Updated map

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -94,11 +94,12 @@
 gulp.task "concat", ->

    vendorFiles = [
-      "#{vendor}/lodash/dist/lodash.compat.js"
-      "#{vendor}/jquery/jquery.js"
-      "#{vendor}/backbone/backbone.js"
-      "#{vendor}/greensock/src/uncompressed/TweenMax.js"
-   ]
+      "lodash/dist/lodash.compat.js"
+      "jquery/jquery.js"
+      "backbone/backbone.js"
+      "greensock/src/uncompressed/TweenMax.js"
+
+   ].map (file) -> "#{vendor}/" + file

    gulp.src vendorFiles
       .pipe concat "vendor.js"


gulpfile update

gulpfile update

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -12,6 +12,8 @@
 require 'coffee-script/register'
 livereloadport = 35729
 serverport = 5000
+
+console.log "process.env.port: " + process.env.port

 paths =
   styles : ['webapp/app/styles/*.css']


tape -> assert

tape -> assert

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -4,11 +4,11 @@
 data    = require 'gulp-data'
 matter  = require 'jade-var-matter'
 jade    = require 'gulp-jade'
+connect = require 'gulp-connect'
 filter  = require 'gulp-filter'
 spy     = require 'through2-spy'
   .obj
 test    = require 'tape'
-connect = require 'gulp-connect'

 processJade = ->                                                          # DRY
   nav  = require './gulp-nav' # convenient during development to wait until now
@@ -34,22 +34,22 @@
       activeMsg = 'Nav should be active.'
       notActiveMsg = 'Nav shouldn\'t be active.'
       Msg = ''
-      test 'Self', (tape) ->
-        tape.is file.nav.title, 'B', titleMsg
-        tape.is file.nav.href, 'b.html', hrefMsg
-        tape.ok file.nav.active, activeMsg
-        tape.end()
-      test 'Parent', (tape) ->
-        tape.is file.nav.parent.title, 'Latin', titleMsg
-        tape.is file.nav.parent.href, '.', hrefMsg
-        tape.notOk file.nav.parent.active, notActiveMsg
-        tape.end()
-      test 'Grandparent', (tape) ->
-        tape.is file.nav.parent.parent.title, 'Home', titleMsg
-        tape.is file.nav.parent.parent.href, '..', hrefMsg
-        tape.notOk file.nav.root.active, notActiveMsg
-        tape.end()
-      test 'Siblings', (tape) ->
+      test 'Self', (assert) ->
+        assert.is file.nav.title, 'B', titleMsg
+        assert.is file.nav.href, 'b.html', hrefMsg
+        assert.ok file.nav.active, activeMsg
+        assert.end()
+      test 'Parent', (assert) ->
+        assert.is file.nav.parent.title, 'Latin', titleMsg
+        assert.is file.nav.parent.href, '.', hrefMsg
+        assert.notOk file.nav.parent.active, notActiveMsg
+        assert.end()
+      test 'Grandparent', (assert) ->
+        assert.is file.nav.parent.parent.title, 'Home', titleMsg
+        assert.is file.nav.parent.parent.href, '..', hrefMsg
+        assert.notOk file.nav.root.active, notActiveMsg
+        assert.end()
+      test 'Siblings', (assert) ->
         for item, i in [
           title: 'A'
           href: 'letter-a.html'
@@ -64,15 +64,16 @@
           active: no
         ]
           current = file.nav.siblings[i]
-          tape.is current.title, item.title, titleMsg
-          tape.is current.href, item.href, hrefMsg
-          tape.is current.active, item.active, if item.active then activeMsg else notActiveMsg
-        tape.end()
-      test 'Children', (tape) ->
-        tape.notOk file.nav.children.length, 'Nav should have no children.'
-        tape.end()
-      test 'Root', (tape) ->
-        tape.is file.nav.root.title, 'Home', titleMsg
-        tape.is file.nav.root.href, '..', hrefMsg
-        tape.notOk file.nav.root.active, notActiveMsg
-        tape.end()
+          assert.is current.title, item.title, titleMsg
+          assert.is current.href, item.href, hrefMsg
+          assert.is current.active, item.active,
+            if item.active then activeMsg else notActiveMsg
+        assert.end()
+      test 'Children', (assert) ->
+        assert.notOk file.nav.children.length, 'Nav should have no children.'
+        assert.end()
+      test 'Root', (assert) ->
+        assert.is file.nav.root.title, 'Home', titleMsg
+        assert.is file.nav.root.href, '..', hrefMsg
+        assert.notOk file.nav.root.active, notActiveMsg
+        assert.end()


Fixed test-karma script

Fixed test-karma script

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -47,7 +47,7 @@
     ]
   .on 'restart', => console.log colors.grey 'The Matrix is reloading...'

-gulp.task 'test-karma', ['scripts:concat'], =>
+gulp.task 'test-karma', =>
   gulp.src ['public/test/**/*']
     .pipe karma(configFile: 'karma.conf.js')
     .on 'error', (error) => console.error error


added template file to watcher/browsersync task

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -53,6 +53,9 @@
   css      :
     main   : 'assets/css/' + dist.name + '.css'
     vendor : []
+  templates:
+    main   : ['**.hbs'
+              'partials/**/**.hbs']

 banner = [ "/**"
            " * <%= pkg.name %> - <%= pkg.description %>"
@@ -105,7 +108,9 @@
 gulp.task 'server', ->
   browserSync.init null,
     proxy: "http://127.0.0.1:#{CONST.PORT.GHOST}"
-    files: ["assets/**/*.*"]
+    files: ["assets/**/*.*",
+            "*.hbs",
+            "partials/**/*.hbs"]
     reloadDelay: 300
     port: CONST.PORT.BROWSERSYNC
   return


Remove imagemin from watch task

Remove imagemin from watch task

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -83,5 +83,4 @@
   gulp.watch '_src/**/*.jade',      ['jade']
   gulp.watch '_src/vendor/**/*.js', ['uglify']
   gulp.watch '_src/sprite/*',       ['sprite']
-  gulp.watch 'images/*',            ['imagemin']
-  return
+  return


add another theme

add another theme

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -37,6 +37,7 @@
     lumendark: 'https://raw.githubusercontent.com/PHELiOX/poi-theme-lumendark/master/lumendark.css'
     paperdark: 'https://raw.githubusercontent.com/ruiii/poi_theme_paper_dark/master/paperdark.css'
     papercyan: 'https://raw.githubusercontent.com/govizlora/theme-papercyan/master/papercyan.css'
+    paperblack: 'https://raw.githubusercontent.com/PHELiOX/paperblack/master/css/paperblack.css'
   for theme, url of themes
     dir = path.join(__dirname, 'assets', 'themes', theme, 'css')
     fs.ensureDirSync dir


Rename properties file

Rename properties file

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -113,6 +113,6 @@
 	gulp.watch ['./coffee/sample/*.coffee', './coffee/sample/*.js', './coffee/sample/*.properties'], ['sample']

 gulp.task 'sample-command', ['setup'], ->
-	gutil.log "./lib/kclhelper.js --print_command --java /usr/bin/java --props ./lib/sample/sample_kclnode_app.properties"
+	gutil.log "./lib/kclhelper.js --print_command --java /usr/bin/java --props ./lib/sample/ml-daemon.properties"

 gulp.task 'default', ['lib', 'sample', 'watch']


fix to lodash linking

fix to lodash linking

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -118,8 +118,7 @@
 gulp.task 'vendor', ->
   gulp.src 'vendor/**/*'
     .pipe gulp.dest "#{out_folder}/"
-  gulp.src 'node_modules/lodash/index.js'
-    .pipe rename "lodash.js"
+  gulp.src 'node_modules/lodash/lodash.js'
     .pipe gulp.dest "#{out_folder}/"
   gulp.src 'node_modules/tv4/tv4.js'
     .pipe rename "tv4.js"


Update gulpfile.cofee

Update gulpfile.cofee

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -9,6 +9,14 @@
 		.pipe gulp.dest './app/public/'

 gulp.task 'typescript-compile', ->
+	gulp.src './src/lib/*.ts'
+		.pipe ts
+			target: 'ES5'
+			removeComments: true
+			noImplicitAny: true
+			declarationFiles: false
+		.js
+		.pipe gulp.dest './app/lib/'
 	gulp.src './src/lib/model/*.ts'
 		.pipe ts
 			target: 'ES5'
@@ -16,7 +24,23 @@
 			noImplicitAny: true
 			declarationFiles: false
 		.js
-		.pipe gulp.dest './app/lib/'
+		.pipe gulp.dest './app/lib/model/'
+	gulp.src './src/lib/web/*.ts'
+		.pipe ts
+			target: 'ES5'
+			removeComments: true
+			noImplicitAny: true
+			declarationFiles: false
+		.js
+		.pipe gulp.dest './app/lib/web/'
+	gulp.src './src/lib/web/models/*.ts'
+		.pipe ts
+			target: 'ES5'
+			removeComments: true
+			noImplicitAny: true
+			declarationFiles: false
+		.js
+		.pipe gulp.dest './app/lib/web/models/'

 gulp.task 'watch', ['build'], ->
 	gulp.watch './src/lib/*.ts', ['typescript-compile']


Simplify gulp file

Simplify gulp file

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -21,25 +21,22 @@
 """

 gulp.task 'build', buildLibraries = (callback) ->
-  gulp.src('config/builds/library/**/*.webpack.config.coffee', {read: false, buffer: false})
+  return gulp.src('config/builds/library/**/*.webpack.config.coffee', {read: false, buffer: false})
     .pipe(webpack())
     .pipe(header(HEADER, {pkg: require './package.json'}))
     .pipe(gulp.dest('.'))
     .on('end', callback)
-  return # promises workaround: https://github.com/gulpjs/gulp/issues/455

 gulp.task 'watch', ['build'], (callback) ->
-  gulp.watch './src/**/*.coffee', (callback) -> buildLibraries(callback)
-  return # promises workaround: https://github.com/gulpjs/gulp/issues/455
+  return gulp.watch './src/**/*.coffee', (callback) -> buildLibraries(callback)

 gulp.task 'minify', ['build'], (callback) ->
-  gulp.src(['*.js', '!*.min.js', '!_temp/**/*.js', '!node_modules/'])
+  return gulp.src(['*.js', '!*.min.js', '!_temp/**/*.js', '!node_modules/'])
     .pipe(uglify())
     .pipe(rename({suffix: '.min'}))
     .pipe(header(HEADER, {pkg: require './package.json'}))
     .pipe(gulp.dest((file) -> file.base))
     .on('end', callback)
-  return # promises workaround: https://github.com/gulpjs/gulp/issues/455

 gulp.task 'start-test-server', (callback) ->
   console.log 'Starting server'
@@ -53,12 +50,11 @@
   require './test/lib/node_jquery_xhr' # ensure that globals for the target backend are loaded
   global.test_parameters = require './test/parameters'
   mocha_options = if options.quick then {grep: '@no_options'} else {}
-  gulp.src(['test/spec/**/*.tests.coffee', 'node_modules/backbone-orm/test/spec/sync/**/*.tests.coffee'])
+  return gulp.src(['test/spec/**/*.tests.coffee', 'node_modules/backbone-orm/test/spec/sync/**/*.tests.coffee'])
     .pipe(mocha(mocha_options))
     .pipe es.writeArray (err, array) ->
       delete global.test_parameters
       (require './test/lib/test_server').release(err); callback(err)
-  return # promises workaround: https://github.com/gulpjs/gulp/issues/455

 testBrowsersFn = (options={}) -> (callback) ->
   (require './test/lib/test_server').retain()


Server: Tests: refactor validateusername test file

--- servers/lib/server/handlers/validateusername.test.coffee
+++ servers/lib/server/handlers/validateusername.test.coffee
@@ -1,28 +1,16 @@
-Bongo                                     = require 'bongo'
-koding                                    = require './../bongo'
-request                                   = require 'request'
-
-{ daisy }                                 = Bongo
-{ expect }                                = require 'chai'
-{ generateRandomString
+{ async
+  expect
+  request
+  generateRandomString
   generateRandomUsername }                = require '../../../testhelper'
 { generateRegisterRequestParams }         = require '../../../testhelper/handler/registerhelper'
 { generateValidateUsernameRequestParams } = require '../../../testhelper/handler/validateusernamehelper'

-JUser                                     = null
+JUser = require '../../../models/user'

 # here we have actual tests
 runTests = -> describe 'server.handlers.validateusername', ->
-
-  beforeEach (done) ->
-
-    # including models before each test case, requiring them outside of
-    # tests suite is causing undefined errors
-    { JUser } = koding.models
-
-    done()
-

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

@@ -30,33 +18,31 @@
       body       :
         username : generateRandomUsername()

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

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

     for method in methods
       addRequestToQueue queue, method

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

   it 'should send HTTP 400 if username is not set', (done) ->

-    validateUsernameRequestParams       = generateValidateUsernameRequestParams()
-    validateUsernameRequestParams.body  = null
+    validateUsernameRequestParams      = generateValidateUsernameRequestParams()
+    validateUsernameRequestParams.body = null

     request.post validateUsernameRequestParams, (err, res, body) ->
-      expect(err)             .to.not.exist
-      expect(res.statusCode)  .to.be.equal 400
-      expect(body)            .to.be.equal 'Bad request'
+      expect(err).to.not.exist
+      expect(res.statusCode).to.be.equal 400
+      expect(body).to.be.equal 'Bad request'
       done()

@@ -68,48 +54,46 @@

     expectedBody = JSON.stringify { kodingUser : false, forbidden : true }
     request.post validateUsernameRequestParams, (err, res, body) ->
-      expect(err)             .to.not.exist
-      expect(res.statusCode)  .to.be.equal 400
-      expect(body)            .to.be.equal expectedBody
+      expect(err).to.not.exist
+      expect(res.statusCode).to.be.equal 400
+      expect(body).to.be.equal expectedBody
       done()

   it 'should send HTTP 400 if username is in use', (done) ->

-    username                      = generateRandomUsername()
+    username = generateRandomUsername()

     validateUsernameRequestParams = generateValidateUsernameRequestParams
       body       :
         username : username

-    registerRequestParams         = generateRegisterRequestParams
+    registerRequestParams = generateRegisterRequestParams
       body       :
         username : username

     queue = [

-      ->
+      (next) ->
         # registering a new user
         request.post registerRequestParams, (err, res, body) ->
-          expect(err)             .to.not.exist
-          expect(res.statusCode)  .to.be.equal 200
-          expect(body)            .to.be.equal ''
-          queue.next()
+          expect(err).to.not.exist
+          expect(res.statusCode).to.be.equal 200
+          expect(body).to.be.equal ''
+          next()

-      ->
+      (next) ->
         # expecting existing username to return 400
         expectedBody = JSON.stringify { kodingUser : true, forbidden : false }
         request.post validateUsernameRequestParams, (err, res, body) ->
-          expect(err)             .to.not.exist
-          expect(res.statusCode)  .to.be.equal 400
-          expect(body)            .to.be.equal expectedBody
-          queue.next()
-
-      -> done()
+          expect(err).to.not.exist
+          expect(res.statusCode).to.be.equal 400
+          expect(body).to.be.equal expectedBody
+          next()

     ]

-    daisy queue
+    async.series queue, done

   it 'should send HTTP 400 if username does not have valid character length', (done) ->
@@ -118,7 +102,7 @@

     queue = [

-      ->
+      (next) ->
         # expecting username that has less then minimum character length to return 400
         validateUsernameRequestParams = generateValidateUsernameRequestParams
           body       :
@@ -126,12 +110,12 @@

         expectedBody = JSON.stringify { kodingUser : false, forbidden : true }
         request.post validateUsernameRequestParams, (err, res, body) ->
-          expect(err)             .to.not.exist
-          expect(res.statusCode)  .to.be.equal 400
-          expect(body)            .to.be.equal expectedBody
-          queue.next()
+          expect(err).to.not.exist
+          expect(res.statusCode).to.be.equal 400
+          expect(body).to.be.equal expectedBody
+          next()

-      ->
+      (next) ->
         # expecting username that has more than maximum character length to return 400
         validateUsernameRequestParams = generateValidateUsernameRequestParams
           body       :
@@ -139,16 +123,14 @@

         expectedBody = JSON.stringify { kodingUser : false, forbidden : true }
         request.post validateUsernameRequestParams, (err, res, body) ->
-          expect(err)             .to.not.exist
-          expect(res.statusCode)  .to.be.equal 400
-          expect(body)            .to.be.equal expectedBody
-          queue.next()
-
-      -> done()
+          expect(err).to.not.exist
+          expect(res.statusCode).to.be.equal 400
+          expect(body).to.be.equal expectedBody
+          next()

     ]

-    daisy queue
+    async.series queue, done

   it 'should send HTTP 200 if username is not in use, not forbidden, has valid range', (done) ->
@@ -161,31 +143,31 @@

     queue = [

-      ->
+      (next) ->
         # expecting username that has average character length to return 200
         validateUsernameRequestParams = generateValidateUsernameRequestParams
           body       :
             username : generateRandomString Math.round (maxLength + minLength) / 2

         request.post validateUsernameRequestParams, (err, res, body) ->
-          expect(err)             .to.not.exist
-          expect(res.statusCode)  .to.be.equal 200
-          expect(body)            .to.be.equal expectedBody
-          queue.next()
+          expect(err).to.not.exist
+          expect(res.statusCode).to.be.equal 200
+          expect(body).to.be.equal expectedBody
+          next()

-      ->
+      (next) ->
         # expecting username that has minimum character length to return 200
         validateUsernameRequestParams = generateValidateUsernameRequestParams
           body       :
             username : generateRandomString minLength

         request.post validateUsernameRequestParams, (err, res, body) ->
-          expect(err)             .to.not.exist
-          expect(res.statusCode)  .to.be.equal 200
-          expect(body)            .to.be.equal expectedBody
-          queue.next()
+          expect(err).to.not.exist
+          expect(res.statusCode).to.be.equal 200
+          expect(body).to.be.equal expectedBody
+          next()

-      ->
+      (next) ->
         # expecting username that has maximum character length to return 200
         validateUsernameRequestParams = generateValidateUsernameRequestParams
           body       :
@@ -195,13 +177,11 @@
           expect(err)             .to.not.exist
           expect(res.statusCode)  .to.be.equal 200
           expect(body)            .to.be.equal expectedBody
-          queue.next()
-
-      -> done()
+          next()

     ]

-    daisy queue
+    async.series queue, done

 runTests()


Add comment explaining why not sending anon data up

--- services/real-time/app/coffee/WebsocketController.coffee
+++ services/real-time/app/coffee/WebsocketController.coffee
@@ -137,7 +137,7 @@
 		], (error, {project_id, first_name, last_name, email, user_id}) ->
 			return callback(error) if error?
 			logger.log {user_id, project_id, client_id: client.id, cursorData: cursorData}, "updating client position"
-
+
 			AuthorizationManager.assertClientCanViewProjectAndDoc client, cursorData.doc_id, (error) ->
 				if error?
 					logger.warn {err: error, client_id: client.id, project_id, user_id}, "silently ignoring unauthorized updateClientPosition. Client likely hasn't called joinProject yet."
@@ -145,10 +145,11 @@
 				cursorData.id      = client.id
 				cursorData.user_id = user_id if user_id?
 				cursorData.email   = email   if email?
+				# Don't store anonymous users in redis to avoid influx
 				if !user_id or user_id == 'anonymous-user'
 					cursorData.name = ""
 					callback()
-				else
+				else
 					cursorData.name = if first_name && last_name
 						"#{first_name} #{last_name}"
 					else if first_name
@@ -166,7 +167,7 @@
 						row:    cursorData.row,
 						column: cursorData.column,
 						doc_id: cursorData.doc_id
-					}, callback)
+					}, callback)
 				WebsocketLoadBalancer.emitToRoom(project_id, "clientTracking.clientUpdated", cursorData)

 	getConnectedUsers: (client, callback = (error, users) ->) ->


Allow start player to play on the whole board

--- app/scripts/index.coffee
+++ app/scripts/index.coffee
@@ -169,6 +169,7 @@
   setup = ->
     teardown()
     game = new UltimateTicTacToe
+    game.lastPlayedPosition = null
     lastAction = null
     [playerX, playerO] = [createPlayerX(), createPlayerO()]
     playerX.setup -> playerO.setup -> next()


Remove console.log

Remove console.log

--- js/contents.coffee
+++ js/contents.coffee
@@ -6,7 +6,6 @@
   headlines = $('.main--content').find('h2, h3')

   $list = $("

")
-  console.log $list.addClass("nav-list")

   for headline in headlines
     text = $(headline).text()


Fix project roles on Talk discussion pages Indentation was wrong for this API call.

--- app/talk/discussion.cjsx
+++ app/talk/discussion.cjsx
@@ -78,16 +78,16 @@
             pathname: @props.location.pathname
             query: @props.location.query

-        if @props.user?
-          talkClient
-            .type 'roles'
-            .get
-              user_id: @props.user.id
-              section: ['zooniverse', @state.discussion.section]
-              is_shown: true
-              page_size: 100
-            .then (roles) =>
-              @setState {roles}
+      if @props.user?
+        talkClient
+          .type 'roles'
+          .get
+            user_id: @props.user.id
+            section: ['zooniverse', @state.discussion.section]
+            is_shown: true
+            page_size: 100
+          .then (roles) =>
+            @setState {roles}

       @setComments(@props.location.query.page ? 1)