Allow routes to set the folder to serve routes from

--- class/Router.coffee
+++ class/Router.coffee
@@ -59,7 +59,7 @@

 	runRoute: (routeObj, serverObj) ->
 		Controller = @loadController routeObj.controller
-		View = "#{@config.defaults.location.views}.#{routeObj.view}$#{@config.defaults.viewextension}"
+		View = "#{routeObj.viewsFolder}.#{routeObj.view}$#{@config.defaults.viewextension}"
 		Layout = "#{@config.defaults.location.layouts}.#{routeObj.layout}$#{@config.defaults.viewextension}"

 		if Controller is false then return @errorHandler 404, routeObj, serverObj
@@ -94,6 +94,7 @@
 			action: action
 			view: view
 			layout: 'main'
+			viewsFolder: @config.defaults.location.views

 		serverObj =
 			res: res
@@ -114,6 +115,7 @@
 				action: action
 				view: view
 				layout: req.params.layout || route.layout
+				viewsFolder: route.viewsFolder || @config.defaults.location.views

 			serverObj =
 				res: res
@@ -129,6 +131,7 @@
 			view: @config.errors[type].view
 			layout: @config.errors[type].layout
 			error: routeObj.error
+			viewsFolder: @config.errors[type].viewsFolder || @config.defaults.location.views

 		@runRoute newRouteObj, serverObj


move participantHandler function outside of module.exports

--- workers/social/lib/social/models/socialapi/socialaccount.coffee
+++ workers/social/lib/social/models/socialapi/socialaccount.coffee
@@ -6,8 +6,6 @@
 module.exports = class SocialAccount extends Base
   JAccount      = require '../account'
   JGroup        = require '../group'
-  JSession      = require '../session'
-  SocialChannel = require './channel'
   Validators    = require '../group/validators'

   { bareRequest } = require './helper'
@@ -58,50 +56,55 @@
     JGroup.on 'MemberAdded', (data) ->
       participantHandler 'addParticipants', data

-    participantHandler = (funcName, data) ->
-      { group, member } = data
-      group.fetchAdmin (err, admin) ->
-        return console.error 'err while fetching admin', err  if err
-        return console.error 'couldnt find admin'  unless admin

-        sessionData = { username: admin.profile.nickname, groupName: group.slug }
-        JSession.fetchSessionByData sessionData, (err, session) ->
-          return console.error 'err while fetching session', err  if err
-          return console.error 'couldnt find a session'  unless session
+participantHandler = (funcName, data) ->

-          client = {}
-          client.sessionToken = session.clientId
-          client.context or= {}
-          client.context.group = group.slug
-          client.context.user  = admin.profile.nickname
-          client.connection or= {}
-          client.connection.delegate  = admin
-          client.connection.groupName = group.slug
+  JSession          = require '../session'
+  SocialChannel     = require './channel'
+  { group, member } = data

-          group.createSocialApiChannels client, (err, socialApiChannels) ->
-            return console.error 'couldnt create socialapi channels', err  if err
-            { socialApiChannelId, socialApiAnnouncementChannelId } = socialApiChannels
+  group.fetchAdmin (err, admin) ->
+    return console.error 'err while fetching admin', err  if err
+    return console.error 'couldnt find admin'  unless admin

-            # ensure member has socialapi id
-            member.createSocialApiId (err, socialApiId) ->
-              return console.error 'couldnt create socialapi id', err  if err
+    sessionData = { username: admin.profile.nickname, groupName: group.slug }
+    JSession.fetchSessionByData sessionData, (err, session) ->
+      return console.error 'err while fetching session', err  if err
+      return console.error 'couldnt find a session'  unless session

-              options =
-                channelId  : socialApiChannelId
-                accountIds : [ socialApiId ]
+      client = {}
+      client.sessionToken = session.clientId
+      client.context or= {}
+      client.context.group = group.slug
+      client.context.user  = admin.profile.nickname
+      client.connection or= {}
+      client.connection.delegate  = admin
+      client.connection.groupName = group.slug

-              SocialChannel[funcName] client, options, (err, participants) ->
+      group.createSocialApiChannels client, (err, socialApiChannels) ->
+        return console.error 'couldnt create socialapi channels', err  if err
+        { socialApiChannelId, socialApiAnnouncementChannelId } = socialApiChannels

-                return console.error "couldnt #{funcName} user into group socialapi chan", err, options  if err
+        # ensure member has socialapi id
+        member.createSocialApiId (err, socialApiId) ->
+          return console.error 'couldnt create socialapi id', err  if err

-                # only add koding's members to announcement channel
-                return if group.slug isnt 'koding'
+          options =
+            channelId  : socialApiChannelId
+            accountIds : [ socialApiId ]

-                options =
-                  channelId  : socialApiAnnouncementChannelId
-                  accountIds : [ socialApiId ]
+          SocialChannel[funcName] client, options, (err, participants) ->

-                SocialChannel[funcName] client, options, (err) ->
-                  return console.error "couldnt #{funcName} user into group socialapi chan", err, options  if err
+            return console.error "couldnt #{funcName} user into group socialapi chan", err, options  if err
+
+            # only add koding's members to announcement channel
+            return if group.slug isnt 'koding'
+
+            options =
+              channelId  : socialApiAnnouncementChannelId
+              accountIds : [ socialApiId ]
+
+            SocialChannel[funcName] client, options, (err) ->
+              return console.error "couldnt #{funcName} user into group socialapi chan", err, options  if err


fixes #66 : Resizing window breaks zoomed camera ratio

--- src/frontend/modules/camera.coffee
+++ src/frontend/modules/camera.coffee
@@ -89,6 +89,7 @@
             newVideo.removeClass 'thumbnailVideo'
             do newVideo[0].play
             container.append newVideo
+            newVideo.css 'margin-left', '-66%'

 if window?
     window.Camera = Camera


Switched Handlebars.SafeString to Utils.safeString

Whoops

--- src/helpers/helpers-numbers.coffee
+++ src/helpers/helpers-numbers.coffee
@@ -66,7 +66,7 @@
       resValue = (bytes / Math.pow(1000, Math.floor(resInt))).toFixed(if resInt < 2 then 0 else 1) # no decimals for anything smaller than 1 MB
       resInt = -1 if bytes is 1 # special case: 1 byte (singular)

-    return new Handlebars.SafeString( resValue+' '+metric[resInt+1] )
+    return Utils.safeString( resValue+' '+metric[resInt+1] )

 module.exports.register = (Handlebars, options) ->


fix issue where `includedAssociation instanceof BrainstemParams` was not working correctly

--- src/loaders/abstract-loader.coffee
+++ src/loaders/abstract-loader.coffee
@@ -257,9 +257,8 @@

         if includedAssociation instanceof Backbone.Collection
           association.collection = includedAssociation
-
           @additionalIncludes.push association
-        else if includedAssociation instanceof BrainstemParams
+        else if includedAssociation.collectionName
           association.brainstemParams = includedAssociation

           @additionalIncludes.push association


_onChannelMessage() test for Remote.Peer

--- src/tests/spec/public/models/remote.peer.spec.coffee
+++ src/tests/spec/public/models/remote.peer.spec.coffee
@@ -384,3 +384,24 @@
 				expect(callArgs).toEqual([
 						fakeChannel
 					])
+
+		describe 'when receiving a message', ->
+			it 'should trigger a message event with the message-data', ->
+				peer = new Peer(fakeController, '1', true, FakeRTCPeerConnection)
+
+				success = false
+				message = new Message('a', 'b', 'event')
+
+				peer.on('message', ( data ) ->
+						expect(data).toBe(message.serialize())
+						success = true
+					)
+
+				peer._onChannelMessage({
+						data: message.serialize()
+					})
+
+				waitsFor(->
+						return success
+					, 1000)
+


ADDED search by skills.

ADDED search by skills.

--- app/assets/javascripts/angular/controllers/SearchCtrl.coffee
+++ app/assets/javascripts/angular/controllers/SearchCtrl.coffee
@@ -15,9 +15,6 @@
     $location.url '/view-profile'

   $scope.searchText = ''
-
-  filterBySkill = (user) ->
-

   $scope.filterByNameOrSkill = (users,searchText) ->
     filteredUsers = []


Improved reload trigger if somebody else changed ticket.

--- app/assets/javascripts/app/controllers/ticket_zoom.js.coffee
+++ app/assets/javascripts/app/controllers/ticket_zoom.js.coffee
@@ -27,9 +27,8 @@
       (data) =>
         update = =>
           if data.id.toString() is @ticket_id.toString()
-            ticket = App.Ticket.find( @ticket_id )
-            @log 'notice', 'TRY', Date(data.updated_at), Date(ticket.updated_at)
-            if Date(data.updated_at) isnt Date(ticket.updated_at)
+            @log 'notice', 'TRY', new Date(data.updated_at), new Date(@ticketUpdatedAtLastCall)
+            if !@ticketUpdatedAtLastCall || ( new Date(data.updated_at).toString() isnt new Date(@ticketUpdatedAtLastCall).toString() )
               @fetch( @ticket_id, false )
         @delay( update, 1800, 'ticket-zoom-' + @ticket_id )
     )


importing bootstrap-sprockets is all we need

--- app/assets/javascripts/comfy/admin/cms/application.js.coffee
+++ app/assets/javascripts/comfy/admin/cms/application.js.coffee
@@ -9,7 +9,6 @@
 #= require codemirror/modes/xml
 #= require codemirror/addons/edit/closetag
 #= require bootstrap-sprockets
-#= require bootstrap
 #= require comfy/admin/cms/lib/bootstrap-datetimepicker
 #= require comfy/admin/cms/lib/diff
 #= require comfy/admin/cms/lib/redactor


Also update changes in local storage.

Also update changes in local storage.

--- app/assets/javascripts/app/lib/app_post/collection.js.coffee
+++ app/assets/javascripts/app/lib/app_post/collection.js.coffee
@@ -162,8 +162,13 @@
       if callback

         # execute callback if record got loaded
+        col = @
         App[type].one 'refresh', ->
           data = App.Collection.find( type, id )
+
+          # load update to local storage
+          col.load( localStorage: true, type: type, data: data )
+
           callback( data )

         # fetch object


fix Collections

fix Collections

--- app/assets/javascripts/uploadcare/utils/collection.js.coffee
+++ app/assets/javascripts/uploadcare/utils/collection.js.coffee
@@ -42,14 +42,7 @@
       super

-  class utils.NotNullUniqCollection extends utils.UniqCollection
-
-    add: (item) ->
-      return unless item?
-      super
-
-
-  class utils.CollectionOfPromises extends utils.NotNullUniqCollection
+  class utils.CollectionOfPromises extends utils.UniqCollection

     constructor: ->
       @onAnyDone = $.Callbacks()
@@ -66,7 +59,7 @@

     add: (item) ->
       unless item and item.done and item.fail and item.then
-        throw new Error('only promises can be added to CollectionOfPromises')
+        return

       super

@@ -81,4 +74,3 @@
         handler(@onAnyFail),
         handler(@onAnyProgress)
       )
-


Avoid console errors when calling setOffset method before visualizationGraphCanvas is initialized

--- app/frontend/javascripts/views/visualization-graph.js.coffee
+++ app/frontend/javascripts/views/visualization-graph.js.coffee
@@ -121,7 +121,8 @@
       @visualizationGraphCanvas.resize()

   setOffset: (offset) ->
-    @visualizationGraphCanvas.setOffset offset
+    if @visualizationGraphCanvas
+      @visualizationGraphCanvas.setOffset offset

   # Collections Events
   onNodesAdd: (node) ->


cross matching done, game logic finish 95%

--- src/main.coffee
+++ src/main.coffee
@@ -16,6 +16,10 @@
     unitX: -> @left + @right + 1

     unitY: -> @top + @bottom + 1
+
+    markX: (point) -> ((1 << @unitX()) - 1) << point.x - @left
+
+    markY: (point) -> ((1 << @unitY()) - 1) << point.y - @top

 class Color
@@ -91,6 +95,7 @@

     constructor: (@grid) ->

+    # TODO: make private
     getCellRange: (cell) ->

         point = cell.get Point
@@ -136,6 +141,24 @@
             if bottom.range.top >= absY and top.range.left >= absX
                 return true

+        [leftMarks, rightMarks] = (getMarkBits item for item in [left, right])
+
+        if result = getBitAdd leftMarks.x, rightMarks.x
+            for i in [0...result.length]
+                list = @grid.getCol i + result.offset
+                count = 0
+                for j in [top.point.y..bottom.point.y]
+                    count++ if list[j].get(State).done
+                return true if count is absY + 1
+
+        if result = getBitAdd leftMarks.y, rightMarks.y
+            for i in [0...result.length]
+                list = @grid.getRow i + result.offset
+                count = 0
+                for j in [left.point.x..right.point.x]
+                    count++ if list[j].get(State).done
+                return true if count is absX + 1
+
         false

     markMatch: (cell) ->
@@ -152,6 +175,24 @@

         cellRange.right?.left = range.unitX()
         cellRange.left?.right = range.unitX()
+
+    getMarkBits = (item) ->
+        x: item.range.markX item.point
+        y: item.range.markY item.point
+
+    # TODO: cache
+    getBitAdd = (a, b) ->
+
+        result = a & b
+
+        return false if result is 0
+
+        result = result.toString(2).split('0').reverse()
+
+        {
+            offset: result.length - 1
+            length: result[result.length - 1].length
+        }

 angular.module('controller')


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -90,4 +90,6 @@
 test "should move player according to their speed", ->
   player.I.speed = 3

+  window.
+
 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -79,6 +79,6 @@

   player.update()

-  ok player.I.facing
+  ok player.I.facing.equal(Point(-1, 0))

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -80,7 +80,7 @@

   equals player.I.y, 21

-
+  window.

 test "should move player according to their speed", ->
   ;


test relative paths

test relative paths

--- test/filelog-test.coffee
+++ test/filelog-test.coffee
@@ -6,9 +6,11 @@
 describe 'filelog', ->
 	before ->
 		process.chdir(path.resolve(__dirname, 'fixtures/filelog'))
+	beforeEach ->
 		@filelog = new Filelog
-	after ->
+	afterEach ->
 		rimraf.sync(@filelog.filename)
+		@filelog = null

 	describe 'adding file references', ->
 		it 'should be persisted to disk', ->
@@ -16,6 +18,12 @@
 			@filelog.add(files)
 			log = JSON.parse(fs.readFileSync(@filelog.filename, 'utf8'))
 			log.should.eql(files)
+	describe 'adding file references with absolute paths', ->
+		it 'should persist relative file paths to disk', ->
+			files = [path.resolve('some/file.js')]
+			@filelog.add(files)
+			log = JSON.parse(fs.readFileSync(@filelog.filename, 'utf8'))
+			log.should.eql(['some/file.js'])
 	describe 'cleaning file references', ->
 		it 'should clear all from disk', ->
 			@filelog.clean()


Improve mocha test for hHttp_out

Improve mocha test for hHttp_out

--- test/hHttpAdapter.coffee
+++ test/hHttpAdapter.coffee
@@ -24,6 +24,7 @@
 #
 should = require("should")
 factory = require "../lib/hfactory"
+validator = require "../lib/validator.coffee"

 describe "hHttpAdapter", ->
   hActor = undefined
@@ -112,8 +113,12 @@
         req.on "data", (data) ->
           body = data
         req.on "end", =>
-          console.log "recept", JSON.stringify(body.toString('utf8'));
-          done()
+          validator.validateHMessage JSON.parse( body.toString('utf8') ), (err, result) =>
+            if not err
+              done()
+            else
+              @owner.log "hMessage not conform : " + JSON.stringify(result)
+

       server.listen 8989, "127.0.0.1"
       hActor.send hActor.buildMessage("urn:localhost:httpOutMochaTest", "hHttpMessage", {hello:"world"})


Client: Activity: Including required classes

--- client/activity/lib/components/sidebarmodallist/index.coffee
+++ client/activity/lib/components/sidebarmodallist/index.coffee
@@ -1,8 +1,10 @@
-React                 = require 'kd-react'
-immutable             = require 'immutable'
-ActivityFlux          = require 'activity/flux'
-Scroller              = require 'app/components/scroller'
-ScrollerMixin         = require 'app/components/scroller/scrollermixin'
+kd             = require 'kd'
+React          = require 'kd-react'
+immutable      = require 'immutable'
+ActivityFlux   = require 'activity/flux'
+Scroller       = require 'app/components/scroller'
+ScrollerMixin  = require 'app/components/scroller/scrollermixin'
+classnames     = require 'classnames'

 module.exports = class SidebarModalList extends React.Component


Get permissions from collaboration data

Get permissions from collaboration data

--- client/ide/lib/views/statusbar/idestatusbaravatarview.coffee
+++ client/ide/lib/views/statusbar/idestatusbaravatarview.coffee
@@ -93,9 +93,11 @@

     appManager.tell 'IDE', 'getCollaborationData', (data) =>

-      { amIHost, settings, watchMap } = data
+      { amIHost, settings, watchMap, permissions } = data

       isWatching  = watchMap.indexOf(@nickname) > -1
+      permission  = permissions.get @nickname
+
       menuWidth   = 172

       if settings.unwatch


adding try-catch

adding try-catch

--- src/poll.coffee
+++ src/poll.coffee
@@ -51,7 +51,7 @@
 			msg.send "@channel: #{text}"
 		catch error
 			msg.send error
-			@robot.logger.info error
+			@robot.logger.error error

 	robot.respond /vote\s+(\d)/i, (msg) ->
 		try
@@ -83,25 +83,27 @@
 			@robot.logger.info "Brain saved"
 			msg.reply "Thanks for your vote"
 		catch error
-			@robot.logger.info error
+			@robot.logger.error error

 	robot.respond /view results/i, (msg) ->
-		@robot.logger.info "View results"
-		topic = @robot.brain.data.poll.topic
-		options = @robot.brain.data.poll.options
-		optionString = ""
-		for index, option of options
-			text = option.text
-			numVotes = if option.users == undefined then 0 else option.users.length
-			users = option.users
-			@robot.logger.info "option: #{option} :: text: #{text} :: count: #{numVotes} :: users: #{users.join(", ")}"
-			optionString += "\t#{index}: #{text} Total: #{numVotes} Users:(#{users.join(", ")})\n"
-		msg.send """
-		#{topic}
-		Current Poll Results:
-		#{optionString}
-		"""
-
+		try
+			@robot.logger.info "View results"
+			topic = @robot.brain.data.poll.topic
+			options = @robot.brain.data.poll.options
+			optionString = ""
+			for index, option of options
+				text = option.text
+				numVotes = if option.users == undefined then 0 else option.users.length
+				users = option.users
+				@robot.logger.info "option: #{option} :: text: #{text} :: count: #{numVotes} :: users: #{users.join(", ")}"
+				optionString += "\t#{index}: #{text} Total: #{numVotes} Users:(#{users.join(", ")})\n"
+			msg.send """
+			#{topic}
+			Current Poll Results:
+			#{optionString}
+			"""
+		catch error
+			@robot.logger.error error
 	robot.respond /poll ping/i, (msg) ->
 		@robot.logger.info "ping called"
 		msg.reply "poll pong"


uniform interface for 'up'

uniform interface for 'up'

--- src/runr.coffee
+++ src/runr.coffee
@@ -11,9 +11,18 @@
   isRunning: mongodb.hasLocalMongoDatabase
 }

+setNow = if typeof setImmediate != 'undefined' then setImmediate else (callback) -> setTimeout(callback, 0)
+
 exports.up = (name, args, callback) ->
   exports[name].isRunning (err, isRunning) ->
-    return callback(err) if err?
-    return callback() if isRunning
+    # We always want to return a function, so the interface remains uniform
+    # If "up" started the process, then calling the function kills if.
+    # If "up" did not start the process, then calling the function does nothing.
+    if err?
+      setNow(-> callback(err))
+      return ->
+    if isRunning
+      setNow(callback)
+      return ->
     exports[name].run(args, callback)


Adding C# support (.cs)

Adding C# support (.cs)


encapsulate comment counting with a separate function

--- src/sloc.coffee
+++ src/sloc.coffee
@@ -129,87 +129,88 @@
 getEmptyLinesCount = (lines) ->
   (i for l,i in lines when whiteSpaceLine.test l).length

+countComments = (lines, cExpr, l, idx=0, startLine, counter={scloc:0, mcloc:0}) ->
+
+  l ?= lines[idx]
+
+  return counter if idx >= lines.length
+
+  if not startLine? and (m = l.match cExpr.startBlock)?
+    startLine = idx
+    countComments lines, cExpr, l.substring(m.index+m[0].length), idx, startLine, counter
+
+  else if startLine? is true and (m = l.match cExpr.stopBlock)?
+    x = idx - startLine + 1
+    counter.mcloc += x
+    countComments lines, cExpr, l.substring(m.index+m[0].length), idx, null, counter
+
+  else
+    counter.scloc++ if not startLine? and cExpr.single.test l
+    countComments lines, cExpr, lines[idx+1], idx+1, startLine, counter
+
+getCommentExpressions = (lang) ->
+
+  # single line comments
+  single =
+    switch lang
+
+      when "coffeescript", "coffee", "python", "py"
+        sharpComment
+      when "javascript", "js", "c", "cc", "java", "php", "php5", "go", "scss", "less", "styl", "stylus"
+        combine doubleSlashComment, singleLineSlashStarComment
+      when "css"
+        singleLineSlashStarComment
+      when "html"
+        singleLineHtmlComment
+      when "lua"
+        doubleHyphenComment
+      else
+        doubleSlashComment
+
+  ## block comments
+  switch lang
+
+    when "coffeescript", "coffee"
+      startBlock = trippleSharpComment
+      stopBlock  = trippleSharpComment
+
+    when "javascript", "js", "c", "cc", "java", "php", "php5", "go", "css", "scss", "less", "styl", "stylus"
+      startBlock = slashStarComment
+      stopBlock  = starSlashComment
+
+    when "python", "py"
+      startBlock = trippleQuoteComment
+      stopBlock  = trippleQuoteComment
+
+    when "html"
+      startBlock = startHtmlComment
+      stopBlock  = stopHtmlComment
+
+    when "lua"
+      startBlock = combine doubleHyphenComment, doubleSquareBracketOpen , ''
+      stopBlock  = combine doubleHyphenComment, doubleSquareBracketClose, ''
+
+    else
+      throw new TypeError "File extension '#{lang}' is not supported"
+
+  { startBlock, stopBlock, single }
+
 slocModule = (code, lang) ->

   unless typeof code is "string"
     throw new TypeError "'code' has to be a string"
-
-  # single line comments
-  switch lang
-
-    when "coffeescript", "coffee", "python", "py"
-      comment = sharpComment
-    when "javascript", "js", "c", "cc", "java", "php", "php5", "go", "scss", "less", "styl", "stylus"
-      comment = combine doubleSlashComment, singleLineSlashStarComment
-    when "css"
-      comment = singleLineSlashStarComment
-    when "html"
-      comment = singleLineHtmlComment
-    when "lua"
-      comment = doubleHyphenComment
-    else
-      comment = doubleSlashComment
-
-  ## block comments
-  switch lang
-
-    when "coffeescript", "coffee"
-      startMultiLineComment = trippleSharpComment
-      stopMultiLineComment  = trippleSharpComment
-
-    when "javascript", "js", "c", "cc", "java", "php", "php5", "go", "css", "scss", "less", "styl", "stylus"
-      startMultiLineComment = slashStarComment
-      stopMultiLineComment  = starSlashComment
-
-    when "python", "py"
-      startMultiLineComment = trippleQuoteComment
-      stopMultiLineComment  = trippleQuoteComment
-
-    when "html"
-      startMultiLineComment = startHtmlComment
-      stopMultiLineComment  = stopHtmlComment
-
-    when "lua"
-      startMultiLineComment = combine doubleHyphenComment, doubleSquareBracketOpen , ''
-      stopMultiLineComment  = combine doubleHyphenComment, doubleSquareBracketClose, ''
-
-    else
-      throw new TypeError "File extension '#{lang}' is not supported"
-
-  nullLineNumbers     = []

   lines = code.split '\n'
   loc   = lines.length
   nloc  = getEmptyLinesCount lines

-  start               = false
-  cCounter            = 0
-  bCounter            = 0
-
-  for l,i in lines
-
-    if start is false and startMultiLineComment.test l
-      start = true
-      startLine = i
-
-    else if start is true and stopMultiLineComment.test l
-      start = false
-      x = i - startLine + 1
-      bCounter += x
-
-    else if start is false and comment.test l
-      cCounter++
-
-  sloc      = loc - cCounter - bCounter - nloc
-  totalC    = cCounter + bCounter
+  { scloc, mcloc } = countComments lines, getCommentExpressions lang
+
+  sloc = loc - scloc - mcloc - nloc
+  cloc = scloc + mcloc

   # result
-  loc:    loc
-  sloc:   sloc
-  cloc:   totalC
-  scloc:  cCounter
-  mcloc:  bCounter
-  nloc:   nloc
+  { loc, sloc, cloc, scloc, mcloc, nloc }

 slocModule.extensions = [
   "coffeescript", "coffee"
@@ -224,8 +225,7 @@
   "scss"
   "less"
   "css"
-  "styl"
-  "stylus"
+  "styl", "stylus"
   "html" ]

 slocModule.keys = keys


Adding replacement as param

Adding replacement as param

--- src/slug.coffee
+++ src/slug.coffee
@@ -73,7 +73,7 @@
 }

-module.exports = slug = (string) ->
+module.exports = slug = (string, replacement = '-') ->
     result = ""
     for char, i in string
         code = string.charCodeAt(i)
@@ -88,6 +88,6 @@
         char = char.replace(/[^\w\s$\*\_\+~\.\(\)\'\"\!\-:@]/g, '') # allowed
         result += char
     result = result.replace(/^\s+|\s+$/g, '') # trim leading/trailing spaces
-    result = result.replace(/[-\s]+/g, '-') # convert spaces
+    result = result.replace(/[-\s]+/g, replacement) # convert spaces
     result.replace(/-$/, '') # remove trailing separator


Initialized the prefs object in the Team class. Fixes #18.

--- src/team.coffee
+++ src/team.coffee
@@ -1,4 +1,4 @@
 class Team
   constructor: (@_client, @id, @name, @domain) ->
-
+    @prefs = {}
 module.exports = Team


improve user-facing response

improve user-facing response

--- src/tell.coffee
+++ src/tell.coffee
@@ -30,7 +30,14 @@
     url = msg.match[1]

     virustotal.getUrlReport url, (err, res) ->
-      return robot.send {room: msg.envelope.user.name}, "#{err}" if err
-      return robot.send {room: msg.envelope.user.name}, "#{res}"
+      return robot.send {room: msg.envelope.user.name}, "Virus total: `#{err.json.verbose_msg}`." if err
+
+      clean = for scanner, obj of res.scans
+        "`#{scanner}`" unless obj.detected
+
+      unclean = for scanner, obj of res.scans
+        "`#{scanner}` (#{obj.result})" if obj.detected
+
+      return robot.send {room: msg.envelope.user.name}, "Virus total: #{res.resource} rated clean by #{clean.join ', '}.  Rated unclean by #{unclean.join ', '}."

     return


Pass basic tests

Pass basic tests

--- src/test.coffee
+++ src/test.coffee
@@ -14,9 +14,9 @@
 {test_god} = require('./test/test_god')

 run_tests = ->
-  test_god(rx, test)
+  test_god(test, mock_rx)

 exports.test = () ->
   console.log "Running tests..."
-  #run_tests()
+  run_tests()
   console.log "...done!"


Turbolinks compatibility

Turbolinks compatibility

--- app/assets/javascripts/contenteditable/contenteditable.js.coffee
+++ app/assets/javascripts/contenteditable/contenteditable.js.coffee
@@ -1,4 +1,4 @@
-$("document").ready ->
+$ ->

   $('[contenteditable]')
     .live 'focus', ->
@@ -12,7 +12,7 @@
         $this.trigger('change')
         $(".contenteditable_save_button").fadeIn(1000)

-  $('.contenteditable_save_button').click ->
+  $(document).on 'click', '.contenteditable_save_button', ->
     c = $("[contenteditable]")
     data1 = {}
     c.each -> data1[$(this).attr('data-tag')] = $(this).text()


use relative path

use relative path

--- app/assets/javascripts/effective_datatables/initialize.js.coffee
+++ app/assets/javascripts/effective_datatables/initialize.js.coffee
@@ -30,7 +30,7 @@
           ]
         aoColumns: datatable.data('widths')
         oTableTools:
-          sSwfPath: '/assets/effective_datatables/copy_csv_xls_pdf.swf',
+          sSwfPath: 'assets/effective_datatables/copy_csv_xls_pdf.swf',
           aButtons: ['copy', 'csv', 'pdf', 'print']
         colVis:
           showAll: 'Show all'


Added styles so that it updates live as well

--- app/assets/javascripts/views/_shared/chart_totals_view.js.coffee
+++ app/assets/javascripts/views/_shared/chart_totals_view.js.coffee
@@ -34,9 +34,12 @@

     list
       .style("height", height)
+      .style("background-image", get_gradient_moz)
+      .style("background-image", get_gradient_webkit)
       .attr("class", get_class)
       .select("span")
         .text(get_value)
+
     list.enter().append("li")
       .attr("class", get_class)
       .style("height", height)


BiodiversityReport form: Fix link text. References #60.

--- app/assets/javascripts/biodiversity_reports.js.coffee
+++ app/assets/javascripts/biodiversity_reports.js.coffee
@@ -11,8 +11,8 @@
     event.preventDefault()

   $('#soil_sample_fields').on 'shown.bs.collapse', (event) ->
-    $('#lnk-toggle-soil-sample-fields').text('Omit')
+    $('#lnk-toggle-soil-sample-fields').text('Omit soil sample')

   $('#soil_sample_fields').on 'hidden.bs.collapse', (event) ->
     $(@).find('input').val('')
-    $('#lnk-toggle-soil-sample-fields').text('Add')
+    $('#lnk-toggle-soil-sample-fields').text('Add soil sample')


html tags get auto-tabbed by 2 but default is 4?

--- app/assets/javascripts/comfy/admin/cms/base.js.coffee
+++ app/assets/javascripts/comfy/admin/cms/base.js.coffee
@@ -99,6 +99,7 @@
   $('textarea[data-cms-cm-mode]').each (i, element) ->
     cm = CodeMirror.fromTextArea element,
       mode:           $(element).data('cms-cm-mode')
+      tabSize:        2
       lineWrapping:   true
       autoCloseTags:  true
       lineNumbers:    true


Update cs.js.coffee

Update cs.js.coffee

--- app/assets/javascripts/uploadcare/locale/cs.js.coffee
+++ app/assets/javascripts/uploadcare/locale/cs.js.coffee
@@ -1 +1,128 @@
+##
+## Czech translation
+##

+uploadcare.namespace 'locale.translations', (ns) ->
+  ns.cs =
+    uploading: 'Nahrávám... Malý moment.'
+    loadingInfo: 'Nahrávám informace...'
+    errors:
+      default: 'Chyba'
+      baddata: 'Neplatná hodnota'
+      size: 'Soubor je příliš velký'
+      upload: 'Nelze nahrát'
+      user: 'Nahrávání zrušeno'
+      info: 'Nelze nahrát informace'
+      image: 'Lze nahrát pouze obrázky'
+      createGroup: 'Nelze vytvořit adresář'
+      deleted: 'Soubor byl smazán'
+    draghere: 'Přetáhněte soubor sem'
+    file:
+      one: '%1 soubor'
+      other: '%1 soubory'
+    buttons:
+      cancel: 'Zrušit'
+      remove: 'Odstranit'
+      choose:
+        files:
+          one: 'Vyberte soubor'
+          other: 'Vyberte soubory'
+        images:
+          one: 'Vyberte obrázek'
+          other: 'Vyberte obrázky'
+    dialog:
+      done: 'Hotovo'
+      showFiles: 'Zobrazit soubory'
+      tabs:
+        names:
+          'empty-pubkey': 'Vítejte'
+          preview: 'Náhled'
+          file: 'Soubor z počítače'
+          url: 'Soubor z internetu'
+          camera: 'Webkamera'
+          facebook: 'Facebook'
+          dropbox: 'Dropbox'
+          gdrive: 'Google Drive'
+          instagram: 'Instagram'
+          vk: 'VK'
+          evernote: 'Evernote'
+          box: 'Box'
+          skydrive: 'OneDrive'
+          flickr: 'Flickr'
+          huddle: 'Huddle'
+        file:
+          drag: 'Přetáhněte soubor sem'
+          nodrop: 'Nahrajte soubory z vašeho počítače'
+          cloudsTip: 'Cloudové úložiště<br>a sociální sítě'
+          or: 'nebo'
+          button: 'Vyberte soubor z počítače'
+          also: 'Můžete také nahrát soubor z'
+        url:
+          title: 'Soubory z internetu'
+          line1: 'Nahrajte jakýkoliv soubor z internetu.'
+          line2: 'Stačí vložit odkaz.'
+          input: 'Odkaz vložte zde...'
+          button: 'Nahrát'
+        camera:
+          capture: 'Pořídit fotografii'
+          mirror: 'Zrcadlo'
+          retry: 'Znovu požádat o povolení'
+          pleaseAllow:
+            title: 'Prosím povolte přístup k webkameře'
+            text: 'Byl(a) jste požádán(a) o přístup k webkameře. ' +
+                  'Abyste mohl(a) pořídit fotografii, musíte přístup povolit.'
+          notFound:
+            title: 'Nebyla nalezena webkamera'
+            text: 'Zdá se, že k tomuto zařízení není připojena žádná webkamera.'
+        preview:
+          unknownName: 'neznámý'
+          change: 'Zrušit'
+          back: 'Zpět'
+          done: 'Přidat'
+          unknown:
+            title: 'Nahrávám... Prosím vyčkejte na náhled.'
+            done: 'Přeskočit náhled a odeslat'
+          regular:
+            title: 'Přidat tento soubor?'
+            line1: 'Tímto přidáte výše vybraný soubor.'
+            line2: 'Prosím potvrďte.'
+          image:
+            title: 'Přidat tento obrázek?'
+            change: 'Zrušit'
+          crop:
+            title: 'Oříznout a přidat tento obrázek'
+            done: 'Hotovo'
+            free: 'zdarma'
+          error:
+            default:
+              title: 'Jejda!'
+              text: 'Něco se v průběhu nahrávání nepodařilo.'
+              back: 'Zkuste to prosím znovu.'
+            image:
+              title: 'Lze nahrávat pouze obrázky.'
+              text: 'Zkuste to prosím s jiným souborem.'
+              back: 'Vyberte obrázek'
+            size:
+              title: 'Soubor přesahuje povolenou velikost.'
+              text: 'Prosím zkuste to s jiným souborem.'
+            loadImage:
+              title: 'Chyba'
+              text: 'Nelze nahrát obrázek'
+          multiple:
+            title: 'Bylo vybráno %files% souborů'
+            question: 'Chcete přidat všechny tyto soubory?'
+            tooManyFiles: 'Bylo vybráno moc souborů. Maximum je %max%'
+            tooFewFiles: 'Bylo vybráno %files% souborů. Musíte vybrat minimálně %min%'
+            clear: 'Odstranit vše'
+            done: 'Hotovo'
+      footer:
+        text: 'Provozováno pomocí'
+        link: 'Uploadcare.com'
+
+
+# Pluralization rules taken from:
+# http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
+uploadcare.namespace 'locale.pluralize', (ns) ->
+  ns.en = (n) ->
+    return 'one' if n == 1
+    'other'


fixed date highlight for date picker; fixes #60

--- app/assets/javascripts/views/log/log_time_view.coffee
+++ app/assets/javascripts/views/log/log_time_view.coffee
@@ -28,7 +28,7 @@

     @ui.inputDate
       .datepicker({format : "yyyy-mm-dd"})
-      .datepicker("setValue", @model.get("dateTime").toDate())
+      .datepicker("setValue", @model.get("dateTime").format("YYYY-MM-DD"))
       .on "changeDate", (evt) =>
         @ui.inputDate.datepicker("hide")


Ask before deleting an environment

Ask before deleting an environment

--- coffee/conda.coffee
+++ coffee/conda.coffee
@@ -89,11 +89,10 @@

     on_activate_click: (event) =>
         env = @envs.get_active()
-        $.ajax({url: "/api/env/#{env.name}/activate", type: 'POST'})
+        $.ajax({url: "/api/env/#{env.get('name')}/activate", type: 'POST'})

     on_delete_click: (event) =>
-        env = @envs.get_active()
-        $.ajax({url: "/api/env/#{env.name}/delete", type: 'POST'})
+        new DeleteEnvView(envs: @envs).show()

     on_clone_click: (event) =>
         new CloneEnvView(envs: @envs).show()
@@ -122,8 +121,10 @@
         $title = $('
').append(@render_title())
         $header = $('
').append([$close, $title])
         $body = $('
').append(@render_body())
-        $submit = $('<button type="submit" class="btn btn-primary"></button>').text(@submit_text()).click(@on_submit)
-        $cancel = $('<button type="button" class="btn btn-default"></button>').text(@cancel_text()).click(@on_cancel)
+        $submit = $('<button type="submit" class="btn"></button>')
+        $submit.addClass("btn-" + @submit_type()).text(@submit_text()).click(@on_submit)
+        $cancel = $('<button type="button" class="btn"></button>')
+        $cancel.addClass("btn-" + @cancel_type()).text(@cancel_text()).click(@on_cancel)
         $footer = $('
').append([$submit, $cancel])
         $content = $('
').append([$header, $body, $footer])
         $dialog = $('
').append($content)
@@ -133,28 +134,48 @@
         $('body').append(@$el)

     render_title: () -> ""
-
     render_body: () -> ""

     submit_text: () -> "Submit"
-
     cancel_text: () -> "Cancel"

+    submit_type: () -> "primary"
+    cancel_type: () -> "default"
+
     on_submit: (event) =>
-
-    on_cancel: (event) =>
+    on_cancel: (event) => @hide()
+
+    on_shown: (event) =>
+    on_hidden: (event) => @remove()
+
+class SettingsView extends ModalView
+
+    render_title: () -> "Settings"
+
+    render_body: () -> "TODO: Settings"
+
+class DeleteEnvView extends ModalView
+
+    initialize: (options) ->
+        @envs = options.envs
+        super(options)
+
+    render_title: () ->
+        "Delete environment"
+
+    render_body: () ->
+        $name = $('').text(@envs.get_active().get('name'))
+        $('').append(["Do you really want to delete ", $name, " environment?"])
+
+    submit_text: () -> "Yes, remove this environment"
+    cancel_text: () -> "No, I changed my mind"
+
+    submit_type: () -> "danger"
+
+    on_submit: (event) =>
+        env = @envs.get_active()
+        $.ajax({url: "/api/env/#{env.get('name')}/delete", type: 'POST'})
         @hide()
-
-    on_shown: (event) =>
-
-    on_hidden: (event) =>
-        @remove()
-
-class SettingsView extends ModalView
-
-    render_title: () -> "Settings"
-
-    render_body: () -> "TODO: Settings"

 class EnvModalView extends ModalView


Optimized thing

Optimized thing

--- coffee/updater.iced
+++ coffee/updater.iced
@@ -9,3 +9,7 @@
   document.getElementById("message").innerHtml = message
   alert(message)
 )
+
+win.on("download-finished", ->
+  alert "Finished!"
+)


php7 ignores files upload when using PUT? Idk

--- resources/assets/coffee/react/profile-page/cover-uploader.coffee
+++ resources/assets/coffee/react/profile-page/cover-uploader.coffee
@@ -15,6 +15,7 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with osu!web.  If not, see <http://www.gnu.org/licenses/>.
 ###
+{form, input} = React.DOM
 el = React.createElement

@@ -25,13 +26,11 @@
       class: 'js-profile-cover-upload fileupload__input'
       type: 'file'
       name: 'cover_file'
-      'data-url': window.updateProfileUrl
       disabled: !@props.canUpload

     $(@refs.uploadButtonContainer).append($uploadButton)

     $uploadButton.fileupload
-      method: 'PUT'
       dataType: 'json'
       dropZone: $dropzone
       submit: ->
@@ -61,8 +60,12 @@
         isSelected: @props.cover.id == null
         name: -1

-      el 'label', className: labelClass, ref: 'uploadButtonContainer',
-        Lang.get 'users.show.edit.cover.upload.button'
+      form
+        action: Url.updateProfileAccount
+        method: 'post'
+        input type: 'hidden', name: '_method', value: 'put'
+        el 'label', className: labelClass, ref: 'uploadButtonContainer',
+          Lang.get 'users.show.edit.cover.upload.button'

       el 'div', className: 'profile-cover-upload-info',
         el 'p', className: 'profile-cover-upload-info-entry',


Fix show more link in user profile

Fix show more link in user profile

Import `i` used for the spinner.

--- resources/assets/coffee/react/profile-page/show-more-link.coffee
+++ resources/assets/coffee/react/profile-page/show-more-link.coffee
@@ -15,7 +15,7 @@
 #    You should have received a copy of the GNU Affero General Public License
 #    along with osu!web.  If not, see <http://www.gnu.org/licenses/>.
 ###
-{a, div} = ReactDOMFactories
+{a, div, i} = ReactDOMFactories
 el = React.createElement

 class ProfilePage.ShowMoreLink extends React.PureComponent


Add tests for _s3DocToMongo

Add tests for _s3DocToMongo

--- services/docstore/test/unit/coffee/DocArchiveManagerTests.coffee
+++ services/docstore/test/unit/coffee/DocArchiveManagerTests.coffee
@@ -212,3 +212,45 @@
 			@DocArchiveManager.unArchiveAllDocs @project_id, (err)=>
 				err.should.equal @error
 				done()
+
+	describe "_s3DocToMongoDoc", ->
+		describe "with the old schema", ->
+			it "should return the docs lines", (done) ->
+				@DocArchiveManager._s3DocToMongoDoc ["doc", "lines"], (error, doc) ->
+					expect(doc).to.deep.equal {
+						lines: ["doc", "lines"]
+					}
+					done()
+
+		describe "with the new schema", ->
+			it "should return the doc lines and ranges", (done) ->
+				@RangeManager.jsonRangesToMongo = sinon.stub().returns {"mongo": "ranges"}
+				@DocArchiveManager._s3DocToMongoDoc {
+					lines: ["doc", "lines"]
+					ranges: {"json": "ranges"}
+					schema_v: 1
+				}, (error, doc) ->
+					expect(doc).to.deep.equal {
+						lines: ["doc", "lines"]
+						ranges: {"mongo": "ranges"}
+					}
+					done()
+
+			it "should return just the doc lines when there are no ranges", (done) ->
+				@DocArchiveManager._s3DocToMongoDoc {
+					lines: ["doc", "lines"]
+					schema_v: 1
+				}, (error, doc) ->
+					expect(doc).to.deep.equal {
+						lines: ["doc", "lines"]
+					}
+					done()
+
+		describe "with an unrecognised schema", ->
+			it "should return an error", (done) ->
+				@DocArchiveManager._s3DocToMongoDoc {
+					schema_v: 2
+				}, (error, doc) ->
+					expect(error).to.exist
+					done()
+


Revert "Don't run package specs"

Revert "Don't run package specs"

This reverts commit fd6e5e7a13153e0b3e069fef828d7853888ec587.

--- tasks/spec-task.coffee
+++ tasks/spec-task.coffee
@@ -47,7 +47,6 @@

     modulesDirectory = path.resolve('node_modules')
     for packageDirectory in fs.readdirSync(modulesDirectory)
-      return callback(null, [])
       packagePath = path.join(modulesDirectory, packageDirectory)
       continue unless grunt.file.isDir(path.join(packagePath, 'spec'))
       continue unless isAtomPackage(packagePath)


add some custom entities

add some custom entities


let gridplots with hover tools function at all

--- bokehjs/src/coffee/common/grid_plot.coffee
+++ bokehjs/src/coffee/common/grid_plot.coffee
@@ -67,13 +67,13 @@
         if tools.length != @get('num_plots')
           continue
         proxy = new _ToolProxy({tools: tools})
-        @get('actions').tools.push(proxy)
+        @get('actions').push(proxy)

       for typ, tools of inspectors
         if tools.length != @get('num_plots')
           continue
         proxy = new _ToolProxy({tools: tools})
-        @get('inspectors').tools.push(proxy)
+        @get('inspectors').push(proxy)

       for et, info of @get('gestures')
         tools = info.tools


Add support for text/literate-coffeescript in the browser

--- browser.coffee
+++ browser.coffee
@@ -17,7 +17,7 @@
 return unless window?

 # Load a remote script from the current domain via XHR.
-CoffeeScript.load = (url, callback) ->
+CoffeeScript.load = (url, callback, options = {}) ->
   xhr = if window.ActiveXObject
     new window.ActiveXObject('Microsoft.XMLHTTP')
   else
@@ -27,7 +27,7 @@
   xhr.onreadystatechange = ->
     if xhr.readyState is 4
       if xhr.status in [0, 200]
-        CoffeeScript.run xhr.responseText
+        CoffeeScript.run xhr.responseText, options
       else
         throw new Error "Could not load #{url}"
       callback() if callback
@@ -38,16 +38,19 @@
 # This happens on page load.
 runScripts = ->
   scripts = document.getElementsByTagName 'script'
-  coffees = (s for s in scripts when s.type is 'text/coffeescript')
+  coffeetypes = ['text/coffeescript', 'text/literate-coffeescript']
+  coffees = (s for s in scripts when s.type in coffeetypes)
   index = 0
   length = coffees.length
   do execute = ->
     script = coffees[index++]
-    if script?.type is 'text/coffeescript'
+    mediatype = script?.type
+    if mediatype in coffeetypes
+      options = {literate: mediatype is 'text/literate-coffeescript'}
       if script.src
-        CoffeeScript.load script.src, execute
+        CoffeeScript.load script.src, execute, options
       else
-        CoffeeScript.run script.innerHTML
+        CoffeeScript.run script.innerHTML, options
         execute()
   null


activity: reformat `NewCommentForm` class definition

--- client/Social/Activity/views/comments/newcommentform.coffee
+++ client/Social/Activity/views/comments/newcommentform.coffee
@@ -1,14 +1,14 @@
 class NewCommentForm extends KDView

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

     options.type       or= "new-comment"
-    options.cssClass   or= "item-add-comment-box"
+    options.cssClass     = KD.utils.curry "item-add-comment-box", options.cssClass
     options.showAvatar  ?= yes

     super options, data

-    @input = new KDHitEnterInputView
+    @input          = new KDHitEnterInputView
       type          : "textarea"
       delegate      : this
       placeholder   : "Type your comment and hit enter..."
@@ -19,7 +19,14 @@
           maxLength : 2000
         messages    :
           required  : "Please type a comment..."
-      callback      : @bound "handleInputEnter"
+      callback      : @bound "enter"
+
+    @input.on "blur", @bound "commentInputReceivedBlur"
+
+    @input.on "focus", =>
+
+      KD.mixpanel "Comment activity, focus"
+      @getDelegate().emit "commentInputReceivedFocus"

   submit: ->
@@ -27,21 +34,7 @@
     @emit "Submit", @input.getValue()

-  viewAppended: ->
-
-    if @getOption "showAvatar"
-      @addSubView new AvatarStaticView
-        size    :
-          width : 42
-          height: 42
-      , KD.whoami()
-
-    @addSubView @input
-
-    @attachListeners()
-
-
-  handleInputEnter: ->
+  enter: ->

     kallback = =>

@@ -60,23 +53,36 @@
       groupName : KD.getGroup().slug

-  attachListeners:->
-    @input.on "blur", @bound "commentInputReceivedBlur"
-    @input.on "focus", =>
-      KD.mixpanel "Comment activity, focus"
-      @getDelegate().emit "commentInputReceivedFocus"
+  makeCommentFieldActive: ->

-  makeCommentFieldActive:->
     @getDelegate().emit "commentInputReceivedFocus"
     (KD.getSingleton "windowController").setKeyView @input

-  resetCommentField:->
-    @getDelegate().emit "CommentViewShouldReset"

-  otherCommentInputReceivedFocus:(instance)->
+  otherCommentInputReceivedFocus:(instance) ->
+
     if instance isnt @input
       commentForm = @input.getDelegate()
       commentForm.resetCommentField() if $.trim(@input.getValue()) is ""

-  commentInputReceivedBlur:->
+
+  commentInputReceivedBlur: ->
+
     @resetCommentField()  if @input.getValue() is ""
+
+
+  resetCommentField: ->
+
+    @getDelegate().emit "CommentViewShouldReset"
+
+
+  viewAppended: ->
+
+    if @getOption "showAvatar"
+      @addSubView new AvatarStaticView
+        size    :
+          width : 42
+          height: 42
+      , KD.whoami()
+
+    @addSubView @input


FinderController:moveFiles remove async.parallel dependency, uses promises

--- client/ide/filetree/controllers/findertreecontroller.coffee
+++ client/ide/filetree/controllers/findertreecontroller.coffee
@@ -286,21 +286,21 @@
       targetNodeView = @nodes[targetNodeView.getData().parentPath]
       targetItem = targetNodeView.getData()

-    stack = []
-    nodesToBeMoved.forEach (node)=>
-      stack.push (cb) =>
-        sourceItem = node.getData()
-        FSItem.move sourceItem, targetItem, (err, response)=>
-          if err then @notify null, null, err
-          else
-            cb err, node
-
-    callback or= (error, result) =>
-      @notify "#{result.length} item#{if result.length > 1 then 's' else ''} moved!", "success"
-      @removeNodeView node for node in result
+    movedNodes = []
+    results = nodesToBeMoved.map (node) ->
+      sourceItem = node.getData()
+      FSItem.move(sourceItem, targetItem).then ->
+        movedNodes.push node
+
+    Promise.all(results).then =>
+      @notify "#{movedNodes.length} item#{if movedNodes.length > 1 then 's' else ''} moved!", "success"
+      @removeNodeView node for node in movedNodes
       @refreshFolder targetNodeView

-    async.parallel stack, callback
+    .catch (err) =>
+      @notify null, null, err
+
+    .nodeify callback

   copyFiles:(nodesToBeCopied, targetNodeView, callback)->


Adding should propagate to NDPEvidenceCollection

--- core/app/backbone/collections/ndp_evidence_collection.coffee
+++ core/app/backbone/collections/ndp_evidence_collection.coffee
@@ -13,7 +13,8 @@
     ]

     for collection in @_containedCollections
-      collection.on 'sync', @loadFromCollections, @
+      @listenTo collection, 'sync', @onCollectionSync
+      @listenTo collection, 'add', @loadFromCollections

   comparator: (item) -> - item.get('impact')

@@ -22,11 +23,14 @@

   fetch: (options={}) ->
     @trigger 'request', this
-    _.invoke @_containedCollections, 'fetch', options
+    _.invoke @_containedCollections, 'fetch', _.extend {}, options, reset: true

-  loadFromCollections: (collectionOrModel) ->
+  onCollectionSync: (collectionOrModel) ->
+    return unless collectionOrModel in @_containedCollections # collection proxies model's sync
+    @loadFromCollections()
+
+  loadFromCollections: ->
     return if @loading()
-    return unless collectionOrModel in @_containedCollections # collection proxies model's sync

     @reset(_.union (col.models for col in @_containedCollections)...)
     @trigger 'sync'


Send events when traversing result pages

--- app/assets/javascripts/rglossa/controllers/search/result_toolbar_controller.coffee
+++ app/assets/javascripts/rglossa/controllers/search/result_toolbar_controller.coffee
@@ -15,6 +15,7 @@

   showPreviousPage: ->
     @set('currentPageNo', @get('currentPageNo') - 1)
+    App.router.send('goToPage')

   showNextPage: ->
     @set('currentPageNo', @get('currentPageNo') + 1)
@@ -22,9 +23,11 @@

   showFirstPage: ->
     @set('currentPageNo', 0)
+    App.router.send('goToPage')

   showLastPage: ->
     @set('currentPageNo', @get('numPages') - 1)
+    App.router.send('goToPage')

   numPages: (->
     numHits = @get('search.numHits')


FileTree: Added context menu items for dropbox feature.

--- client/landing/app/MainApp/filetree/controllers/findercontextmenucontroller.coffee
+++ client/landing/app/MainApp/filetree/controllers/findercontextmenucontroller.coffee
@@ -79,6 +79,8 @@
         action                    : 'createFile'
       'New Folder'                :
         action                    : 'createFolder'
+      'Upload to Dropbox'         :
+        action                    : 'dropboxSaver'
       'Upload file...'            :
         disabled                  : yes
         action                    : 'upload'
@@ -144,6 +146,13 @@
       Download                    :
         disabled                  : yes
         action                    : "download"
+        separator                 : yes
+      Dropbox                     :
+        children                  :
+          'Download from Dropbox' :
+            action                : 'dropboxChooser'
+          'Upload to Dropbox'     :
+            action                : 'dropboxSaver'
         separator                 : yes
       'Public URL...'             :
         separator                 : yes


fancy code not needed

fancy code not needed

--- vendor/assets/javascripts/fustrate/components/file_picker.coffee
+++ vendor/assets/javascripts/fustrate/components/file_picker.coffee
@@ -10,7 +10,4 @@

     document.body.appendChild(input)

-    event = document.createEvent 'HTMLEvents'
-    event.initEvent 'click', true, false
-
-    input.dispatchEvent event
+    input.click()


2 js errors fixed in the Saved Queries UI ( https://github.com/leikind/wice_grid/issues/89 )

--- vendor/assets/javascripts/wice_grid_saved_queries_init.js.coffee
+++ vendor/assets/javascripts/wice_grid_saved_queries_init.js.coffee
@@ -88,13 +88,13 @@
   queryListId  = "##{gridName}_query_list"
   inputField.val('') if queryName
   if errorMessages = data['error_messages']
-    $(notificationMessagesDomId).text(errorExplanation)
+    $(notificationMessagesDomId).text(errorMessages)
   else
     if notificationMessages = data['notification_messages']
       $(notificationMessagesDomId).text(notificationMessages)
     $(gridTitleId).html("<h3>#{queryName}</h3>") if queryName
     $(queryListId).replaceWith(data['query_list'])
-    $(queryListId).effect('highlight')
+    $(queryListId).effect('highlight') if jQuery.ui

     $(".wice-grid-delete-query", $(queryListId)).click (event) ->
       deleteQuery(this, event)


refactor step and repeat

refactor step and repeat

--- test/plotter_test.coffee
+++ test/plotter_test.coffee
@@ -46,7 +46,7 @@
         p.region = true
         p.tools.D10 = {}
         (-> p.command { set: { currentTool: 'D10' } }).should.throw /tool/
-    it 'should the interpolation mode', ->
+    it 'should set the interpolation mode', ->
       p.command { set: { mode: 'i' } }
       p.mode.should.eql 'i'
       p.command { set: { mode: 'cw' } }
@@ -66,6 +66,27 @@
     it 'should set the file end flag', ->
       p.command { set: { done: true } }
       p.done.should.be.true
+
+  describe 'new layer commands', ->
+    it 'should finish any in progress layer', ->
+      p.current = [ 'stuff' ]
+      p.command { new: { sr: { x: 2, y: 3, xStep: 7, yStep: 2 } } }
+      p.current.should.eql []
+      p.current = [ 'more', 'stuff' ]
+      p.command { new: { layer: 'C' } }
+      p.current.should.eql []
+
+    it 'should set step repeat params', ->
+      p.command { new: { sr: { x: 2, y: 3, xStep: 7, yStep: 2 } } }
+      p.stepRepeat.should.eql { x: 2, y: 3, xStep: 7, yStep: 2 }
+      p.command { new: { sr: { x: 1, y: 1 } } }
+      p.stepRepeat.should.eql { x: 1, y: 1, xStep: 0, yStep: 0 }
+
+    it 'should set polarity param', ->
+      p.command { new: { layer: 'C' } }
+      p.polarity.should.eql 'c'
+      p.command { new: { layer: 'D' } }
+      p.polarity.should.eql 'd'

   describe 'defining new tools', ->
     it 'should add a standard tool to the tools object', ->


Fixed bug in angularjs service.

Fixed bug in angularjs service.

Now alerts that arise during folder management should be displayed properly.

--- app/assets/javascripts/ng-services/ng-FolderMgmtSvc.js.coffee
+++ app/assets/javascripts/ng-services/ng-FolderMgmtSvc.js.coffee
@@ -3,8 +3,8 @@
 ########################################################

 angular.module('feedbunch').service 'folderMgmtSvc',
-['$rootScope', '$http', 'findSvc', 'currentFeedSvc',
-($rootScope, $http, findSvc, currentFeedSvc)->
+['$rootScope', '$http', 'findSvc', 'currentFeedSvc', 'timerFlagSvc',
+($rootScope, $http, findSvc, currentFeedSvc, timerFlagSvc)->

   #--------------------------------------------
   # PRIVATE FUNCTION: Update the model to account for a feed having been removed from a folder


Update example.pre.coffee

Update example.pre.coffee

--- demo/input/example.pre.coffee
+++ demo/input/example.pre.coffee
@@ -60,8 +60,7 @@
 	  a:4, b:5, c:6
 	return {input, output, first, second}

-macrosTagsAndCallbackTypes ->
-	type callback = (err: boolean, result: string) -> void
+macros ->
 	macro require -> arguments[0].split(",").map(token) -> string :: "var ${token} = require(${token})"
 	tag graph, formula = (strings, values) -> strings.join("")
 	tag jsx {*} = (strings, values) -> strings.map(token) -> values[token] || token


fix (Drawers) remove bogus validation on preloaded web view id that was preventing the update API from working properly

--- src/models/DrawerCollection.coffee
+++ src/models/DrawerCollection.coffee
@@ -63,29 +63,28 @@
   update: (options={}, callbacks={}) ->
     steroids.debug "steroids.drawers.update called"

-    parameters = {
+    parameters =
       left:{}
       right:{}
       options:{}
-    }

     validViews = true

     if options.left?
-      if options.left.id?
-        DrawerCollection.applyViewOptions options.left, parameters.left
-      else
+      if ! options.left.id? and options.left.location
         validViews = false
         if callbacks.onFailure?
           callbacks.onFailure "No identifier provided for the preloaded webview!"
+      else
+        DrawerCollection.applyViewOptions options.left, parameters.left

     if options.right?
-      if options.right.id?
-        DrawerCollection.applyViewOptions options.right, parameters.right
-      else
+      if ! options.right.id? and options.right.location
         validViews = false
         if callbacks.onFailure?
           callbacks.onFailure "No identifier provided for the preloaded webview!"
+      else
+        DrawerCollection.applyViewOptions options.right, parameters.right

     if options.options?
       DrawerCollection.applyDrawerSettings options.options, parameters.options
@@ -153,7 +152,8 @@
   @applyViewOptions: (view={}, parameters={}) ->
     if view.id?
       parameters.id = view.id
-    else
+
+    if view.location?
       parameters.url = view.location

     if view.keepLoading == true


fix(markdown): Fix tabbing into composer

--- internal_packages/composer-markdown/lib/markdown-editor.cjsx
+++ internal_packages/composer-markdown/lib/markdown-editor.cjsx
@@ -12,6 +12,11 @@
 class MarkdownEditor extends React.Component
   @displayName: 'MarkdownEditor'

+  @containerRequired: false
+
+  @contextTypes:
+    parentTabGroup: React.PropTypes.object,
+
   @propTypes:
     body: React.PropTypes.string.isRequired,
     onBodyChanged: React.PropTypes.func.isRequired,
@@ -24,17 +29,17 @@
       showIcons: ['code', 'table']
     )
     @mde.codemirror.on "change", @_onBodyChanged
+    @mde.codemirror.on "keydown", @_onKeyDown
     @mde.value(Utils.getTextFromHtml(@props.body))
+    @focus()

   componentWillReceiveProps: (newProps) =>
     currentText = Utils.getTextFromHtml(@props.body)
     if @props.body isnt newProps.body and currentText.length is 0
       @mde.value(Utils.getTextFromHtml(newProps.body))
-      @mde.codemirror.execCommand('goDocEnd')

   focus: =>
     @mde.codemirror.focus()
-    @mde.codemirror.execCommand('goDocEnd')

   focusAbsoluteEnd: =>
     @focus()
@@ -51,17 +56,31 @@
     setImmediate =>
       @props.onBodyChanged(target: {value: @mde.value()})

+  _onKeyDown: (codemirror, e)=>
+    if e.key is 'Tab' and e.shiftKey is true
+      position = codemirror.cursorCoords(true, 'local')
+      isAtBeginning = position.top <= 5 and position.left <= 5
+      if isAtBeginning
+        # TODO i'm /really/ sorry
+        # Subject is at position 2 within the tab group, the focused text area
+        # in this component is at position 17, so that's why we shift back 15
+        # positions.
+        # This will break if the dom elements between here and the subject ever
+        # change
+        @context.parentTabGroup.shiftFocus(-15)
+        e.preventDefault()
+        e.codemirrorIgnore = true
+
   render: ->
     # TODO sorry
     # Add style tag to disable incompatible plugins
-

+

       <textarea
         ref="textarea"


return true threw warning

return true threw warning

--- urb/zod/pub/work/src/js/components/ItemComponent.coffee
+++ urb/zod/pub/work/src/js/components/ItemComponent.coffee
@@ -37,9 +37,7 @@
       e.preventDefault()
       return

-  onFocus: (e) ->
-    @props._focus e,@
-    return true
+  onFocus: (e) -> @props._focus e,@

   _markDone: (e) ->
     WorkActions.setItem @props.item,'done',(not (@props.item.done is true))


adjusts the example_spec template to point to the correct fixture.

--- lib/generators/jasminerice/templates/example_spec.js.coffee
+++ lib/generators/jasminerice/templates/example_spec.js.coffee
@@ -3,7 +3,7 @@
 #= require bar

 describe "Foo", ->
-  loadFixtures 'baz' # located at 'spec/javascripts/fixtures/baz.html.haml'
+  loadFixtures 'example_fixture' # located at 'spec/javascripts/fixtures/example_fixture.html.haml'
   it "it is not bar", ->
     v = new Foo()
     expect(v.bar()).toEqual(false)
@@ -11,4 +11,4 @@
 describe "Bar", ->
   it "it is not foo", ->
     v = new Bar()
-    expect(v.foo()).toEqual(false)+    expect(v.foo()).toEqual(false)


Align labels to the left

Align labels to the left

--- static/coffee/v2_polls.coffee
+++ static/coffee/v2_polls.coffee
@@ -358,12 +358,17 @@
               enabled: true
               inside: true
               align: "left"
+              useHTML: true
+              crop: false
+              overflow: 'allow'
               padding: 10
               style: {
                 color: '#333'
                 fontWeight: 'bold'
                 fontSize: '0.75rem'
                 textOutline: false
+                width: '180px'
+                overflow: 'hidden'
               }
               formatter: ->
                 return this.point.name.toUpperCase()


fix reversed inputs in FF; fixes #328

fix reversed inputs in FF; fixes #328

--- app/assets/javascripts/admin/views/pagination_view.coffee
+++ app/assets/javascripts/admin/views/pagination_view.coffee
@@ -109,8 +109,8 @@
       filterQuery = @ui.inputSearch.val()
       app.vent.trigger("paginationView:filter", filterQuery)

+      @ui.inputSearch.focus()
       @ui.inputSearch.val(filterQuery)
-      @ui.inputSearch.focus()

   collectionSynced : (evt) ->


Reduced rerender calls.

Reduced rerender calls.

--- app/assets/javascripts/app/controllers/ticket_zoom.coffee
+++ app/assets/javascripts/app/controllers/ticket_zoom.coffee
@@ -169,6 +169,10 @@
         if !@doNotLog
           @doNotLog = 1
           @recentView( 'Ticket', ticket_id )
+
+        # scroll to end of page
+        if force
+          @scrollToBottom()

       error: (xhr) =>
         statusText = xhr.statusText
@@ -233,7 +237,7 @@
     @ticket.article = undefined

     # render page
-    @render(force)
+    @render()

   positionPageHeaderStart: =>

@@ -273,13 +277,13 @@
     @highlighed = true
     @highligher.loadHighlights()

-  render: (force) =>
+  render: =>

     # update taskbar with new meta data
     App.Event.trigger 'task:render'
     @formEnable( @$('.submit') )

-    if force || !@renderDone
+    if !@renderDone
       @renderDone = true
       @html App.view('ticket_zoom')(
         ticket:     @ticket
@@ -348,10 +352,9 @@
     # show article
     if !@article_view
       @article_view = new App.TicketZoomArticleView(
-        ticket:      @ticket
-        el:          @el.find('.ticket-article')
-        ui:          @
-        highlighter: @highlighter
+        ticket: @ticket
+        el:     @el.find('.ticket-article')
+        ui:     @
       )

     @article_view.execute(
@@ -368,7 +371,8 @@

     @ticketLastAttributes = @ticket.attributes()

-    if @shown
+    if @shown && !@initDone
+      @initDone = true

       # inital load of highlights
       @loadHighlighter()
@@ -589,7 +593,9 @@
     # submit changes
     ticket.save(
       done: (r) =>
-        @renderDone = false
+
+        # enable form
+        @formEnable(e)

         # reset article - should not be resubmited on next ticket update
         ticket.article = undefined


__switchTab, __addTab -> switchTab, addTab

--- app/assets/javascripts/uploadcare/widget/dialog.js.coffee
+++ app/assets/javascripts/uploadcare/widget/dialog.js.coffee
@@ -71,21 +71,21 @@
       __prepareTabs: ->
         @tabs = {}

-        @tabs.preview = @__addTab 'preview'
+        @tabs.preview = @addTab 'preview'
         @tabs.preview.onDone.add =>
           @dfd.resolve @currentFile
         @tabs.preview.onBack.add =>
           @__setFile null

         for tabName in @settings.tabs when tabName not of @tabs
-          @tabs[tabName] = @__addTab(tabName)
+          @tabs[tabName] = @addTab(tabName)
           if @tabs[tabName]
             @tabs[tabName].onSelected.add (fileType, data) =>
               @__setFile ns.fileFrom @settings, fileType, data
           else
             throw "No such tab: #{tabName}"

-        @__switchTab(@settings.tabs[0])
+        @switchTab(@settings.tabs[0])

       __closeDialog: ->
         @content.fadeOut 'fast', => @content.off().remove()
@@ -95,7 +95,7 @@
           @currentFile.startUpload()
           @tabs.preview.setFile @currentFile
           @__showTab 'preview'
-          @__switchTab 'preview'
+          @switchTab 'preview'
         else
           @__hideTab 'preview'

@@ -118,7 +118,7 @@
         $('<li>')
           .addClass("uploadcare-dialog-tab-#{name}")
           .attr('title', t("tabs.#{name}.title"))
-          .on('click', => @__switchTab(name))
+          .on('click', => @switchTab(name))
           .appendTo(@content.find('.uploadcare-dialog-tabs'))

         tab.setContent $('<div>')
@@ -149,6 +149,6 @@

       __hideTab: (tab) ->
         if @currentTab == tab
-          @__switchTab @settings.tabs[0]
+          @switchTab @settings.tabs[0]
         @content.find(".uploadcare-dialog-tab-#{tab}").hide()


Fix root selector

Fix root selector

--- assets/javascripts/react/components/root_component.coffee
+++ assets/javascripts/react/components/root_component.coffee
@@ -15,7 +15,7 @@
       <option value={root} key={root}>{root}</option>

     <span>
-      <select id={@props.id}{'selectRoot'} className='selectRoot' onChange={@handleChange} ref="selectRoot" defaultValue={@props.selected}>
+      <select id={@props.id}{'selectRoot'} className='selectRoot' onChange={@handleChange} ref="selectRoot" value={@props.selected}>
         {options}
       </select>
     </span>


Fix multi locales row disabled state for whole click-area

--- app/assets/javascripts/app/lib/app_post/multi_locales_row.coffee
+++ app/assets/javascripts/app/lib/app_post/multi_locales_row.coffee
@@ -65,7 +65,8 @@
   _updateButtons: (value, is_solo, is_primary) ->
     is_deleted = @el.hasClass('settings-list--deleted')

-    @removeButton.attr('disabled',    is_solo || !value || is_primary)
+    @removeButton.attr('disabled', is_solo || !value || is_primary)
+    @removeButton.parent().toggleClass('is-disabled', is_solo || !value || is_primary)
     @primaryCheckbox.attr('disabled', is_solo || !value || is_deleted)
     @primaryCheckbox.prop('checked' , is_primary)


fix sparkline value display when there are multiple hosts

--- app/assets/javascripts/states/monitoring/helpers/graph.js.coffee
+++ app/assets/javascripts/states/monitoring/helpers/graph.js.coffee
@@ -75,16 +75,14 @@

   # show value text
   # calc offset
-  wrapper = $(el).offsetParent()
-  oX = wrapper.offset().left
-  if pX > 50
-    vr = wrapper.width()-x-oX
-    opts = { right: vr+"px", left: "auto" }
+  opts = if pX > 50
+    vr = $(el).offsetParent().width()-x-15
+    { right: vr+"px", left: "auto" }
   else
-    opts = { left: pX+"px", right: "auto" }
-
-  # show for ever graph
-  $("div.graph").each (i, el) ->
+    { left: pX+"px", right: "auto" }
+
+  # show for every graph
+  $(el).parents("div.wrapper").find("div.graph").each (i, el) ->
     gg = $(el).data("graph")
     yVal = Bixby.monitoring.find_value_near_x(gg, pX)
     yVal = Bixby.monitoring.format_value(yVal) if yVal != "n/a"


adding size input placeholder

adding size input placeholder

--- app/assets/javascripts/templates/product_form_template.js.coffee
+++ app/assets/javascripts/templates/product_form_template.js.coffee
@@ -85,7 +85,7 @@

                 Sizes

-                    <input type="text" size="30" name="product[sizes]" id="product_sizes" class="input-xlarge">
+                    <input placeholder='Enter a size and click +' type="text" size="30" name="product[sizes]" id="product_sizes" class="input-xlarge">


Fixed injectios for derectives

Fixed injectios for derectives

--- app/assets/javascripts/todo/directives/focus_directive.js.coffee
+++ app/assets/javascripts/todo/directives/focus_directive.js.coffee
@@ -1,8 +1,9 @@
 angular.module 'Todo'
-  .directive 'todoFocus', ($timeout) ->
+  .directive 'todoFocus', [ '$timeout', ($timeout) ->
     (scope, elem, attrs) ->
       scope.$watch attrs.todoFocus, (newVal) ->
         if (newVal)
           $timeout () ->
             elem[0].focus()
-          , 0, false+          , 0, false
+  ]


Make CompoundPlayer inherit from Player.

--- application/chrome/content/wesabe/download/CompoundPlayer.coffee
+++ application/chrome/content/wesabe/download/CompoundPlayer.coffee
@@ -1,18 +1,13 @@
 extend = require 'lang/extend'
+Player = require 'download/Player'

-class CompoundPlayer
-  @register: (params) ->
-    klass = @create params
-
-    # make sure we put it where wesabe.require expects it
-    wesabe.provide "fi-scripts.#{klass.fid}", klass
-
-    return klass
-
-  @create: (params) ->
+class CompoundPlayer extends Player
+  @create: (params, callback) ->
     class klass extends this
       @fid: params.fid
       @org: params.org
+
+    callback? klass

     extend klass.prototype, params


Client:Ticker: moved event functions to separate functions and bounded them to event

--- client/Social/Activity/ContentDisplays/ActivityTicker.coffee
+++ client/Social/Activity/ContentDisplays/ActivityTicker.coffee
@@ -18,55 +18,60 @@
     @load()

     group = KD.getSingleton("groupsController")
-    group.on "MemberJoinedGroup", (data)=>
-      {member} = data
-      return console.warn "member is not defined in new member event"  unless member
+    group.on "MemberJoinedGroup", @bound "addJoin"
+    group.on "LikeIsAdded", @bound "addLike"
+    group.on "FollowHappened", @bound "addFollow"

-      {constructorName, id} = member
-      KD.remote.cacheable constructorName, id, (err, account)=>
-        return console.error "account is not found", err if err or not account
-        @listController.addItem {as: "member", target: account}, 0
+  addJoin: (data)->
+    {member} = data
+    return console.warn "member is not defined in new member event"  unless member

-    group.on "LikeIsAdded", (data)=>
-      {liker, origin, subject} = data
-      unless liker and origin and subject
-        return console.warn "data is not valid"
+    {constructorName, id} = member
+    KD.remote.cacheable constructorName, id, (err, account)=>
+      return console.error "account is not found", err if err or not account
+      @listController.addItem {as: "member", target: account}, 0

-      {constructorName, id} = liker
-      KD.remote.cacheable constructorName, id, (err, source)=>
-        return console.log "account is not found", err, liker if err or not source
+  addFollow: (data)->
+    {follower, origin} = data
+    return console.warn "data is not valid"  unless follower and origin

-        {_id:id} = origin
-        KD.remote.cacheable "JAccount", id, (err, target)=>
-          return console.log "account is not found", err, origin if err or not target
+    {constructorName, id} = follower
+    KD.remote.cacheable constructorName, id, (err, source)=>
+      return console.log "account is not found" if err or not source
+      {_id:id, bongo_:{constructorName}} = data.origin
+      KD.remote.cacheable constructorName, id, (err, target)=>
+        return console.log "account is not found" if err or not target
+        eventObj = {source, target, as:"follower"}

-          {constructorName, id} = subject
-          KD.remote.cacheable constructorName, id, (err, subject)=>
-            return console.log "subject is not found", err, data.subject if err or not subject
+        # following tag has its relationship flipped!!!
+        if constructorName is "JTag"
+          eventObj =
+            source : target
+            target : source
+            as     : "follower"

-            eventObj = {source, target, subject, as:"like"}
-            @listController.addItem eventObj, 0
+        @listController.addItem eventObj, 0

-    group.on "FollowHappened", (data)=>
-      {follower, origin} = data
-      return console.warn "data is not valid"  unless follower and origin
+  addLike: (data)->
+    {liker, origin, subject} = data
+    unless liker and origin and subject
+      return console.warn "data is not valid"

-      {constructorName, id} = follower
-      KD.remote.cacheable constructorName, id, (err, source)=>
-        return console.log "account is not found" if err or not source
-        {_id:id, bongo_:{constructorName}} = data.origin
-        KD.remote.cacheable constructorName, id, (err, target)=>
-          return console.log "account is not found" if err or not target
-          eventObj = {source, target, as:"follower"}
+    {constructorName, id} = liker
+    KD.remote.cacheable constructorName, id, (err, source)=>
+      return console.log "account is not found", err, liker if err or not source

-          # following tag has its relationship flipped!!!
-          if constructorName is "JTag"
-            eventObj =
-              source : target
-              target : source
-              as     : "follower"
+      {_id:id} = origin
+      KD.remote.cacheable "JAccount", id, (err, target)=>
+        return console.log "account is not found", err, origin if err or not target

+        {constructorName, id} = subject
+        KD.remote.cacheable constructorName, id, (err, subject)=>
+          return console.log "subject is not found", err, data.subject if err or not subject
+
+          eventObj = {source, target, subject, as:"like"}
           @listController.addItem eventObj, 0
+

   load: ->
     lastItem = @listController.getItemsOrdered().last


Clean up test name

Clean up test name

--- test/css.coffee
+++ test/css.coffee
@@ -52,7 +52,7 @@
       cases.compare(@nodes, 'css.remove')
       names.should.eql ['coloring', 'moving']

-  describe 'eachDeclaration', ->
+  describe 'eachDeclaration()', ->

     it 'should iterate declarations', ->
       nodes = cases.load('css.declarations')


should include new title too.

should include new title too.

--- lib/assets/javascripts/google-analytics-turbolinks.js.coffee
+++ lib/assets/javascripts/google-analytics-turbolinks.js.coffee
@@ -4,8 +4,8 @@
     # Google Analytics
     if window.ga != undefined
       ga('set', 'location', location.href.split('#')[0])
-      ga('send', 'pageview')
+      ga('send', 'pageview', {"title": document.title})
     else if window._gaq != undefined
       _gaq.push(['_trackPageview'])
     else if window.pageTracker != undefined
-      pageTracker._trackPageview();+      pageTracker._trackPageview();


all tests passing

all tests passing

--- test/log.coffee
+++ test/log.coffee
@@ -1,4 +1,5 @@
 should = require 'should'
+logger = require 'torch'

 bus = require '../lib/bus'
 core = require '..'
@@ -31,7 +32,7 @@

           # We should receive a log message
           should.exist data
-          data.should.eql color
+          data.should.eql [color]
           done()

         # When we log it via the given topic
@@ -40,18 +41,18 @@
 methodTests = [
   method: 'init'
   args: ['config', 'retriever']
-  expected: "Calling 'core.init' with args: { config: 'config', retriever: 'retriever' }"
+  expected: "Calling 'core.init' with args: { config: { blacklist: [], timeout: 2000, general: {} },\n  retriever: \n   { projectRoot: '/Users/brandon/Projects/Code/js/axiom',\n     rel: [Function],\n     retrieve: [Function],\n     retrieveExtension: [Function] } }"
  ,
   method: 'reset'
   args: []
   expected: "Calling 'core.reset'"
  ,
   method: 'load'
-  args: ['moduleName']
-  expected: "Calling 'core.load' with args: { moduleName: 'moduleName' }"
+  args: ['extensionName']
+  expected: "Calling 'core.load' with args: { extensionName: 'extensionName' }"
  ,
   method: 'request'
-  args: ['channel', 'data']
+  args: ['channel', 'data', ->]
   expected: "Calling 'core.request' with args: { channel: 'channel', data: 'data' }"

  # this includes a UUID now and is non-trivial to test
@@ -90,9 +91,9 @@
           should.exist data

           # With the expected details
-          data.should.eql expected
+          data.should.eql [expected]

           done()

         # When we call the given public 'core' method
-        try core[method](args...)
+        core[method] args...


update

update

--- app/assets/javascripts/components/click_edit_events_components.js.coffee
+++ app/assets/javascripts/components/click_edit_events_components.js.coffee
@@ -1,7 +1,7 @@
 Caminio.ClickEditEventsComponent = Ember.Component.extend

   venue: null
-  starts: moment().add(1,'h').minutes(0).toDate()
+  value: null

   saveClickComponent: (e)->
     return unless $('.editing-click-form').length
@@ -13,7 +13,7 @@
     @_super()
     @set('origValue', '')
     @set('origValue', @get('value')) unless Em.isEmpty(@get('value'))
-    @set('value','') if Em.isEmpty(@get('value'))
+    @set('value',moment().add(1,'h').minutes(0).toDate()) if Em.isEmpty(@get('value'))

     $(document)
       .off('click', $.proxy(@saveClickComponent, @))
@@ -27,27 +27,27 @@
   saveActionName: 'save'

   timeValue: ((key,val)->
-    return moment().format('HH')+':00' if !val && !@get('starts')
+    return moment().format('HH')+':00' if !val && !@get('value')
     if arguments.length > 1
-      @set 'starts', moment() unless @get('starts')
-      @set 'starts', moment(@get('starts')).hours(val.split(':')[0]).minutes(val.split(':')[1]).toDate()
-    moment(@get('starts')).format('HH:mm')
-  ).property 'starts'
+      @set 'value', moment() unless @get('value')
+      @set 'value', moment(@get('value')).hours(val.split(':')[0]).minutes(val.split(':')[1]).toDate()
+    moment(@get('value')).format('HH:mm')
+  ).property 'value'

   dateValue: ((key,val)->
     return moment().format('DD-MM-YYYY') if !val && !@get('value')
     hours = moment().format('HH')
     minutes = 0
-    if @get 'starts'
-      hours = moment(@get('starts')).hours()
-      minutes = moment(@get('starts')).minutes()
-    if arguments.length > 1
+    if @get 'value'
+      hours = moment(@get('value')).hours()
+      minutes = moment(@get('value')).minutes()
+    if val
       d = moment(val, 'DD-MM-YYYY')
       d.hours(hours) if hours
       d.minutes(minutes) if hours
-      @set 'starts', d.toDate()
-    moment(@get('starts')).format('DD-MM-YYYY')
-  ).property 'starts'
+      @set 'value', d.toDate()
+    moment(@get('value')).format('DD-MM-YYYY')
+  ).property 'value'

   monthValue: (->
     moment(@get('value')).format('MMMM')
@@ -124,7 +124,7 @@
   selectEvent: (date)->
     entry = @get('targetObject.lineup_entry')
     events = entry.get('lineup_events')
-    lineupEvent = @get('targetObject').store.createRecord 'lineup_event', starts: date, lineup_venue: @get('venue'), lineup_entry: entry
+    lineupEvent = @get('targetObject').store.createRecord 'lineup_event', value: date, lineup_venue: @get('venue'), lineup_entry: entry

   setupQuickEventForm: (modal)->
     $(modal).find('.datepicker').pikaday
@@ -193,4 +193,4 @@
   actions:

     save: ->
-      console.log 'here save in select_events_item_controller'+      console.log 'TODO: save in select_events_item_controller'


Replaces H. to @ on CSRF helpers

Replaces H. to @ on CSRF helpers

--- app/assets/javascripts/javascript_renderer/helpers/csrf.jsh.coffee
+++ app/assets/javascripts/javascript_renderer/helpers/csrf.jsh.coffee
@@ -1,5 +1,5 @@
 helper 'csrfMetaTags', () ->
-  if H.csrfToken
-    H.contentTag('meta', name: 'csrf-param', content: 'authenticity_token') + H.contentTag('meta', name: 'csrf-token', content: H.csrfToken)
+  if @csrfToken
+    @contentTag('meta', name: 'csrf-param', content: 'authenticity_token') + @contentTag('meta', name: 'csrf-token', content: @csrfToken)
   else
     ''


Sentence selection at least as good as before but still has some bugs

--- annotran/static/scripts/annotator/plugin/sentenceselection.coffee
+++ annotran/static/scripts/annotator/plugin/sentenceselection.coffee
@@ -17,6 +17,7 @@
     this.currentSentence = 0
     this.storedEvent = null
     this.extentElement = null
+    this.savedOffset = 0

     null

@@ -55,20 +56,15 @@
     full_xpath = Util.xpathFromNode($(initialTarget), document)
     end_xpath = Util.xpathFromNode($(endTarget), document)

-    if startIndexOffset != 0
-      data = {
-        start: full_xpath
-        startOffset: startIndexOffset
-        end: end_xpath
-        endOffset: endIndex + 1
-      }
-    else
-      data = {
-        start: full_xpath
-        startOffset: this.currentIndex
-        end: end_xpath
-        endOffset: endIndex + 1
-      }
+
+    data = {
+      start: full_xpath
+      startOffset: startIndexOffset + this.savedOffset
+      end: end_xpath
+      endOffset: endIndex + 1
+    }
+
+    this.savedOffset = 0

     return data

@@ -80,6 +76,10 @@
     desiredText = $(currentTarget).text()
     match = /[.!?]/.test(desiredText)
     desiredText = desiredText.split(/[.!?]/)
+
+    finalCount = desiredText.length - 1
+
+    matchEnd = desiredText[this.currentSentence] == ""

     offset_to_use = 0
     counter = 0
@@ -103,6 +103,12 @@

     if endIndex > $(currentTarget).text().length - 1
       endIndex = $(currentTarget).text().length - 1
+
+    if (this.currentSentence == finalCount) and (matchEnd == false)
+      # if this is the case then we are starting mid-element and need to jump
+      match = false
+      if this.savedOffset == 0
+        this.savedOffset = offset_to_use

     # test if we have found a sentence marker or if we are forcing this through anyway
     if (match or force) and (offset_to_use < endIndex)
@@ -133,6 +139,7 @@
         if nextSibling != undefined and nextSibling.length != 0
           this.selectSentence initialTarget, nextSibling
         else
+          # this needs to gracefully fall through
           this.selectSentence initialTarget, nextSibling

   findASentence: (event = {}) =>


Disable saving when items are all empty

Disable saving when items are all empty

--- app/assets/javascripts/components/inline_edit_body_part.js.coffee
+++ app/assets/javascripts/components/inline_edit_body_part.js.coffee
@@ -7,10 +7,14 @@
     @set('snapshot', Em.copy(@get('block'), true))
   ).observes('editing')

-  hasContent: true
-  # hasContent: Em.computed.notEmpty('bodyPart.value')
+  hasContent: (->
+    @get('block').any(@_isEmpty)
+  ).property('block.@each.value')

   hasNoContent: Em.computed.not('hasContent')
+
+  _isEmpty: (item) ->
+    item && !Ember.isEmpty(item.value)

   actions:
     toggleEdit: ->


removed some comments

removed some comments

--- client/app/Applications/StartTab.kdapplication/views/appthumbview.coffee
+++ client/app/Applications/StartTab.kdapplication/views/appthumbview.coffee
@@ -311,30 +311,4 @@
       @hideLoader()
       unless err
         @destroy()
-        KD.track "Apps", "RemoveShortcutClicked"
-
-  # click:(event)->
-
-  #   return if $(event.target).closest('.icon-container').length > 0
-
-  #   {type, name, path} = @getData()
-  #   path = name if not path
-
-  #   appManager = KD.getSingleton "appManager"
-  #   router     = KD.getSingleton "router"
-
-  #   couldntCreate = =>
-  #     appManager.off "AppCouldntBeCreated", appCreated
-  #     @hideLoader()
-
-  #   appCreated    = =>
-  #     appManager.off "AppCreated", couldntCreate
-  #     KD.track "Apps", "ApplicationRun", manifest.name
-  #     @hideLoader()
-
-  #   appManager.once "AppCouldntBeCreated", couldntCreate
-  #   appManager.once "AppCreated", appCreated
-
-  #   router.handleRoute path
-  #   return no
-
+        KD.track "Apps", "RemoveShortcutClicked"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/number_extensions.coffee
+++ test/number_extensions.coffee
@@ -118,6 +118,6 @@
   ]

   4.circularPoints (p, i) ->
-    ok p.x.toFixed(2) .equal(points[i]), "#{p} == #{points[i]}"
+    o p.x.toFixed(2) .equal(points[i]), "#{p} == #{points[i]}"

 module undefined


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/number_extensions.coffee
+++ test/number_extensions.coffee
@@ -118,6 +118,6 @@
   ]

   4.circularPoints (p, i) ->
-    equals p.x.toFixed(5), points[i].x.toFixed(5), "#{p} == #{points[i]}"
+    equals p.x.toFixed(5), points[i].x.toFixed(5), "#{p.x} == #{points[i]}"

 module undefined


More string extensions

More string extensions

--- test/string_extensions.coffee
+++ test/string_extensions.coffee
@@ -1,16 +1,4 @@
 module "String"
-
-test "#parse", ->
-  equals "true".parse(), true, "parsing 'true' should equal boolean true"
-  equals "false".parse(), false, "parsing 'true' should equal boolean true"
-  equals "7.2".parse(), 7.2, "numbers should be cool too"
-
-  equals '{"val": "a string"}'.parse().val, "a string", "even parsing objects works"
-
-test "#constantize", ->
-  equals "String".constantize(), String, "look up a constant"
-  equals "Math".constantize(), Math, "look up a constant"
-  equals "Number".constantize(), Number, "look up a constant"

 test "#blank", ->
   equals "  ".blank(), true, "A string containing only whitespace should be blank"
@@ -18,5 +6,27 @@
   equals "  a ".blank(), false
   equals "  \n\t ".blank(), true

+test "#camelize", ->
+  equals "active_record".camelize(), "activeRecord"
+
+test "#constantize", ->
+  equals "String".constantize(), String, "look up a constant"
+  equals "Math".constantize(), Math, "look up a constant"
+  equals "Number".constantize(), Number, "look up a constant"
+
+test "#humanize", ->
+  equals "employee_salary".humanize(), "Employee salary"
+
+test "#parse", ->
+  equals "true".parse(), true, "parsing 'true' should equal boolean true"
+  equals "false".parse(), false, "parsing 'true' should equal boolean true"
+  equals "7.2".parse(), 7.2, "numbers should be cool too"
+
+  equals '{"val": "a string"}'.parse().val, "a string", "even parsing objects works"
+
+test "#titleize", ->
+  equals "man from the boondocks".titleize(), "Man From The Boondocks"
+  equals "x-men: the last stand".titleize(), "X Men: The Last Stand"
+
 module()


Nothing to test in clinical.

Nothing to test in clinical.

--- test/unit/clinicalSpec.coffee
+++ test/unit/clinicalSpec.coffee
@@ -1,61 +0,0 @@
-define ['ngmocks', 'moment', 'clinical'], (ngmocks, moment, clinical) ->
-  describe 'Unit Testing Clinical Service', ->
-    # The qiprofile Clinical Profile factory.
-    Clinical = null
-
-    beforeEach ->
-      # Fake the clinical service.
-      angular.mock.module('qiprofile.clinical')
-      inject ['Clinical', (_Clinical_) ->
-        Clinical = _Clinical_
-      ]
-
-    describe 'Clinical Profile', ->
-      # FIXME - test is broken with the REST model changes.
-      # TODO - change mock to conform with REST model and
-      # rerun after clinical is refactored per the app TODOs.
-      mock =
-        # TODO - Use the resource data model to create the mock subject.
-        subject:
-          # The anonymized birth date is July 7.
-          birth_date: moment([1985, 6, 7])
-          races: ['Black', 'Asian']
-          ethnicity: 'Non-Hispanic'
-          encounters:
-            [
-              {
-                date: moment([2014, 2, 12])
-                outcomes: [
-                  # TODO - grade is an aggregate.
-                  # tnm: {
-                  #   grade: 2
-                  #   lymphStatus: 3
-                  #   metastasis: true
-                  #   size: "pT3"
-                  # }
-                ]
-                encounter_type: "Biopsy"
-              }
-              {
-                date: moment([2014, 4, 6])
-                outcomes: [
-                  # tnm: {
-                  #   #
-                  #   grade: 3
-                  #   lymphStatus: 2
-                  #   metastasis: true
-                  #   size: "pT2"
-                  # }
-                ]
-                encounter_type: "Assessment"
-              }
-            ]
-
-      config = null
-
-      beforeEach ->
-        # The Clinical Profile configuration.
-        config = Clinical.configureProfile(mock.subject)
-
-      it.only 'should set the open flag', ->
-        expect(config.open, "Open flag is not true").to.be.true


Zotero uses library+keys or itemIDs inconsistently -- I need to be able to find stuff consistently, add itemID when missing

--- chrome/content/zotero-better-bibtex/serialized.coffee
+++ chrome/content/zotero-better-bibtex/serialized.coffee
@@ -1,4 +1,14 @@
 Components.utils.import("resource://zotero/config.js")
+
+Zotero.BetterBibTeX.itemToExportFormat = (item) ->
+  json = Zotero.Utilities.Internal.itemToExportFormat(item)
+
+  if !json.itemID
+    if item.libraryID && item.libraryID != '0'
+      json.itemID = Zotero.DB.valueQuery('select ROWID from items where libraryID is NULL and key = ?', [item.key])
+    else
+      json.itemID = Zotero.DB.valueQuery('select ROWID from items where libraryID = ? and key = ?', [item.libraryID, item.key])
+  return json

 Zotero.BetterBibTeX.serialized =
   items: {}
@@ -67,7 +77,7 @@
       item ||= Zotero.Items.get(itemID)

       # TODO: force legacy format to true until I switch over
-      items[itemID] = (if item.isAttachment() then @_attachmentToArray(item) else Zotero.Utilities.Internal.itemToExportFormat(item)) if item
+      items[itemID] = (if item.isAttachment() then @_attachmentToArray(item) else Zotero.BetterBibTeX.itemToExportFormat(item)) if item

       switch
         # the serialization yielded no object (why?), mark it as missing so we don't do this again


explicit return on auth service isValidSignup

--- client/shared/services/auth_service.coffee
+++ client/shared/services/auth_service.coffee
@@ -58,3 +58,5 @@
     if _.keys(user.errors)
       user.name           = vars.name
       user.legalAccepted  = vars.legalAccepted
+
+    return _.keys(user.errors).length == 0


test: click code block area and see Try on Koding button

--- client/test/lib/dashboard/utilities.coffee
+++ client/test/lib/dashboard/utilities.coffee
@@ -53,3 +53,7 @@
       .pause 5000
       .waitForElementVisible codeBlockSelector, 20000
       .assert.valueContains textarea, user.teamSlug
+      .click codeBlockSelector
+      .waitForElementVisible '.kdnotification', 20000
+      .assert.containsText '.kdnotification', 'Copied to clipboard!'
+      .waitForElementVisible tryOnKodingButtonSelector, 20000


Make the camera lerp towards its target position based on dt

--- src/public/scripts/app.game.coffee
+++ src/public/scripts/app.game.coffee
@@ -220,7 +220,7 @@
 				targetPosition = @player.position.clone().add(cameraDirection.multiplyScalar(80))

 				# Ease the camera to the target position
-				@camera.position.lerp(targetPosition, .03)
+				@camera.position.lerp(targetPosition, 1.5 * dt)

 				# Set the upvector perpendicular to the planet surface and point the camera
 				# towards the player


move cursor position to the end of contenteditable div after model is changed

--- src/radians/contenteditable.coffee
+++ src/radians/contenteditable.coffee
@@ -25,6 +25,18 @@
     ctrl.$render = ->
       old_render() if old_render != null # old_render? leads to linted js
       elmt.html ctrl.$viewValue
+      # move cursor to the end
+      el = elmt.get(0)
+      range = document.createRange()
+      sel = window.getSelection()
+      if el.childNodes.length > 0
+        el2 = el.childNodes[el.childNodes.length - 1]
+        range.setStartAfter(el2)
+      else
+        range.setStartAfter(el)
+      range.collapse(true)
+      sel.removeAllRanges()
+      sel.addRange(range)
       null

     null


Demos: Updated teamwork code on demos page.

--- client/app/Applications/Demos.kdapplication/AppController.coffee
+++ client/app/Applications/Demos.kdapplication/AppController.coffee
@@ -20,7 +20,7 @@
       joinModalTitle     : "Join a coding session"
       joinModalContent   : "
Paste the session key that you received and start coding together.
"
       shareSessionKeyInfo: "
This is your session key, you can share this key with your friends to work together.
"
-      firebaseInstances  : "teamwork-local"
+      firebaseInstance   : "teamwork-local"
       # firebaseInstances  : ["kd-prod-1", "kd-prod-2", "kd-prod-3", "kd-prod-4", "kd-prod-5"]
       enableChat         : yes
       panels             : [


improved url handling

improved url handling

--- builder.coffee
+++ builder.coffee
@@ -15,7 +15,8 @@
 	constructor: ->
 		window.main = this

-		@preCheckedItems = document.location.hash.substr(1).split('-')
+		if document.location.hash
+			@preCheckedItems = document.location.hash.substr(1).split('-')

 		baseUrl = 'https://api.github.com/repos/lega911/angular-light/contents/'
 		sources = {


Fixed misleading button instruction

Fixed misleading button instruction

--- views/app.cjsx
+++ views/app.cjsx
@@ -397,7 +397,7 @@
       <OverlayTrigger placement='right' overlay={<Tooltip id='poi-screenshot-button'>{__ 'Take a screenshot'}</Tooltip>}>
         <Button onClick={@handleCapturePage} bsSize='small'><FontAwesome name='camera-retro' /></Button>
       </OverlayTrigger>
-      <OverlayTrigger placement='right' overlay={<Tooltip id='poi-volume-button'>{if @state.muted then __ 'Volume off' else __ 'Volume on'}</Tooltip>}>
+      <OverlayTrigger placement='right' overlay={<Tooltip id='poi-volume-button'>{if @state.muted then __ 'Volume on' else __ 'Volume off'}</Tooltip>}>
         <Button onClick={@handleSetMuted} bsSize='small' className={if @state.muted then 'active' else ''}><FontAwesome name={if @state.muted then 'volume-off' else 'volume-up'} /></Button>
       </OverlayTrigger>
     </div>


unfocus

unfocus

--- spec/operator-transform-string-spec.coffee
+++ spec/operator-transform-string-spec.coffee
@@ -1240,7 +1240,7 @@
           f\n
           """

-  fdescribe "Change Order faimliy: Reverse, Sort, SortCaseInsensitively, SortByNumber", ->
+  describe "Change Order faimliy: Reverse, Sort, SortCaseInsensitively, SortByNumber", ->
     beforeEach ->
       atom.keymaps.add "test",
         'atom-text-editor.vim-mode-plus:not(.insert-mode)':


Consolidate events and frames

Consolidate events and frames

--- src/trix/watchdog/recording.coffee
+++ src/trix/watchdog/recording.coffee
@@ -1,16 +1,19 @@
 class Trix.Watchdog.Recording
-  @fromJSON: ({snapshots, events, frames}) ->
-    new this snapshots, events, frames
+  @fromJSON: ({snapshots, frames}) ->
+    new this snapshots, frames

-  constructor: (@snapshots = [], @events = [], @frames = []) ->
+  constructor: (@snapshots = [], @frames = []) ->

   recordSnapshot: (snapshot) ->
     snapshotJSON = JSON.stringify(snapshot)
     if snapshotJSON isnt @lastSnapshotJSON
-      @events.push(type: "snapshot")
+      @lastSnapshotJSON = snapshotJSON
       @snapshots.push(snapshot)
-      @lastSnapshotJSON = snapshotJSON
-      @recordFrame()
+      @recordEvent(type: "snapshot")
+
+  recordEvent: (event) ->
+    frame = [@getTimestamp(), @snapshots.length - 1, event]
+    @frames.push(frame)

   getSnapshotAtIndex: (index) ->
     @snapshots[index] if index >= 0
@@ -19,33 +22,17 @@
     snapshotIndex = @getSnapshotIndexAtFrameIndex(frameIndex)
     @getSnapshotAtIndex(snapshotIndex)

-  recordEvent: (event) ->
-    @events.push(event)
-    @recordFrame()
-
-  getEventAtIndex: (index) ->
-    @events[index] if index >= 0
-
-  getEventsUpToIndex: (index, size = 0) ->
-    return [] if index < 0
-    @events.slice(0, index + 1).slice(-size)
-
-  getEventsUpToFrameIndex: (frameIndex, size) ->
-    eventIndex = @getEventIndexAtFrameIndex(frameIndex)
-    @getEventsUpToIndex(eventIndex, size)
-
-  recordFrame: ->
-    frame = [@getTimestamp(), @snapshots.length - 1, @events.length - 1]
-    @frames.push(frame)
-
   getTimestampAtFrameIndex: (index) ->
     @frames[index]?[0]

   getSnapshotIndexAtFrameIndex: (index) ->
     @frames[index]?[1]

-  getEventIndexAtFrameIndex: (index) ->
+  getEventAtFrameIndex: (index) ->
     @frames[index]?[2]
+
+  getEventsUpToFrameIndex: (index) ->
+    frame[2] for frame in @frames.slice(0, index + 1)

   getFrameCount: ->
     @frames.length
@@ -54,20 +41,14 @@
     new Date().getTime()

   truncateToSnapshotCount: (snapshotCount) ->
-    snapshotOffset = @snapshots.length - snapshotCount
-    eventOffset = null
-    return if snapshotOffset < 0
+    offset = @snapshots.length - snapshotCount
+    return if offset < 0

     frames = @frames
-    @frames = for [timestamp, snapshotIndex, eventIndex] in frames when snapshotIndex >= snapshotOffset
-      if eventIndex isnt -1
-        eventOffset ?= eventIndex + 1
-        eventIndex -= eventOffset
-      snapshotIndex -= snapshotOffset
-      [timestamp, snapshotIndex, eventIndex]
+    @frames = for [timestamp, index, event] in frames when index >= offset
+      [timestamp, index - offset, event]

-    @events = @events.slice(eventOffset) if eventOffset?
-    @snapshots = @snapshots.slice(snapshotOffset)
+    @snapshots = @snapshots.slice(offset)

   toJSON: ->
-    {@snapshots, @events, @frames}
+    {@snapshots, @frames}


Refactor shipping-rate-selection directive to remove annoying refresh issue

--- app/assets/javascripts/sprangular/directives/shippingRatesSelection.coffee
+++ app/assets/javascripts/sprangular/directives/shippingRatesSelection.coffee
@@ -21,22 +21,8 @@

       order.updateTotals()

+      Checkout.setDelivery()
+
     $scope.$watch('order.shippingAddress', ->
       $scope.address = $scope.order.shippingAddress
     , true)
-
-    validateAddress = (address) ->
-      $scope.isValid = address.firstname && address.lastname && address.city && address.address1 && address.zipcode && address.country && address.state && address.phone
-
-    $scope.$watch('address', validateAddress, true)
-
-    # use $scope.$watchGroup when its released
-    $scope.$watch 'address.country.id + address.state.id + address.zipcode + isValid', (oldValue, newValue) ->
-      return if $scope.loading || oldValue == newValue || !$scope.isValid
-
-      $scope.loading = true
-
-      Checkout.setDelivery().then ->
-        $scope.loading = false
-
-    validateAddress($scope.address)


vm: dont show always on, sharing and edit name for disabled stack machines

--- client/home/lib/virtualmachines/components/virtualmachineslist/view.coffee
+++ client/home/lib/virtualmachines/components/virtualmachineslist/view.coffee
@@ -75,9 +75,9 @@
           shouldRenderDetails={yes}
           shouldRenderSpecs={yes}
           shouldRenderPower={yes}
-          shouldRenderAlwaysOn={yes}
-          shouldRenderSharing={yes}
-          shouldRenderEditName={yes}
+          shouldRenderAlwaysOn={not stack.get 'disabled'}
+          shouldRenderSharing={not stack.get 'disabled'}
+          shouldRenderEditName={not stack.get 'disabled'}
           onChangeAlwaysOn={@lazyBound 'onChangeAlwaysOn', machine}
           onChangePowerStatus={@lazyBound 'onChangePowerStatus', machine}
           onSharedWithUser={@lazyBound 'onSharedWithUser', machine}


Added keyup validation to permission setting input.

--- client/landing/app/MainApp/filetree/itemsubviews/setpermissionsview.coffee
+++ client/landing/app/MainApp/filetree/itemsubviews/setpermissionsview.coffee
@@ -127,7 +127,15 @@

     @addSubView @newModeInput = new KDInputView
       cssClass : "new-mode-input"
-      keyup    : =>
+      validate      :
+        event       : "keyup"
+        rules       :
+          required  : yes
+          maxLength : 3
+        messages    :
+          required  : "File permission mode required"
+          maxLength : "New file permission mode must be 3 chars"
+      keyup         : =>
         value = Encoder.XSSEncode @newModeInput.getValue()
         if value > 99 and value < 778
           @setPermission value, =>