fixes bug in previous implementation

fixes bug in previous implementation

--- source/server/infrastructure/router.coffee
+++ source/server/infrastructure/router.coffee
@@ -54,12 +54,14 @@
       @eventBus.subscribeTo @initializingMessage, (event) =>
         @log.info("#{this}: Creating new #{@eventSourceable} with event
                   #{event.typeName()}\n", event)
-        @repository.save(@_handleDomainErrors -> new @eventSourceable(event))
+        eventSourceable = @_handleDomainErrors(-> new @eventSourceable event)
+        @repository.save(eventSourceable) if eventSourceable?
     else if @initializingMessage.isSubclassOf(Space.messaging.Command)
       @commandBus.registerHandler @initializingMessage, (cmd) =>
         @log.info("#{this}: Creating new #{@eventSourceable} with command
                   #{cmd.typeName()}\n", cmd)
-        @repository.save(@_handleDomainErrors -> new @eventSourceable(cmd))
+        eventSourceable = @_handleDomainErrors(-> new @eventSourceable cmd)
+        @repository.save(eventSourceable) if eventSourceable?

   _routeEventToEventSourceable: (eventType) ->
     @eventBus.subscribeTo eventType, @_genericEventHandler
@@ -75,7 +77,8 @@
                        #{@eventSourceable}<#{correlationId}>\n"), event)
     eventSourceable = @repository.find @eventSourceable, correlationId
     throw Router.ERRORS.cannotHandleMessage(event) if !eventSourceable?
-    @repository.save(@_handleDomainErrors -> eventSourceable.handle(event))
+    eventSourceable = @_handleDomainErrors(-> eventSourceable.handle event)
+    @repository.save(eventSourceable) if eventSourceable?

   _genericCommandHandler: (command) =>
     if not command? then return
@@ -83,7 +86,8 @@
                        #{@eventSourceable}<#{command.targetId}>"), command)
     eventSourceable = @repository.find @eventSourceable, command.targetId
     throw Router.ERRORS.cannotHandleMessage(command) if !eventSourceable?
-    @repository.save(@_handleDomainErrors -> eventSourceable.handle(command))
+    eventSourceable = @_handleDomainErrors(-> eventSourceable.handle command)
+    @repository.save(eventSourceable) if eventSourceable?

   _logMsg: (message) -> "#{@configuration.appId}: #{this}: #{message}"

@@ -95,3 +99,4 @@
         thrower: @eventSourceable.toString(),
         error: error
       }))
+      return null


Used only for debugging.

Used only for debugging.

--- packages/middleware/tests.coffee
+++ packages/middleware/tests.coffee
@@ -2,8 +2,6 @@

 Users = new Meteor.Collection USERS_COLLECTION
 LogActions = new Meteor.Collection 'LogActionsActions_meteor_middleware_tests'
-
-Error.stackTraceLimit = Infinity;

 if Meteor.isServer
   # Initialize the database


AJ-1573 Apply GradeRubricsCtrl patch

AJ-1573 Apply GradeRubricsCtrl patch

--- app/assets/javascripts/angular/controllers/GradeRubricCtrl.js.coffee
+++ app/assets/javascripts/angular/controllers/GradeRubricCtrl.js.coffee
@@ -11,6 +11,31 @@
     $scope.assignmentId = assignmentId
     $scope.studentId = studentId
     $scope.addMetrics(metrics)
+
+  # distill key/value pairs for metric ids and relative order
+  $scope.pointsAssigned = ()->
+    points = 0
+    angular.forEach($scope.metrics, (metric, index)->
+      points += metric.max_points if metric.max_points
+    )
+    points or 0
+
+  $scope.pointsDifference = ()->
+    $scope.pointsPossible - $scope.pointsGiven()
+
+  $scope.pointsRemaining = ()->
+    pointsRemaining = $scope.pointsDifference()
+    if pointsRemaining > 0 then pointsRemaining else 0
+
+  # Methods for identifying point deficit/overage
+  $scope.pointsMissing = ()->
+    $scope.pointsDifference() > 0 and $scope.pointsGiven() > 0
+
+  $scope.pointsSatisfied = ()->
+    $scope.pointsDifference() == 0 and $scope.pointsGiven() > 0
+
+  $scope.pointsOverage = ()->
+    $scope.pointsDifference() < 0

   $scope.showMetric = (attrs)->
     new MetricPrototype(attrs)


BO submit namespace when creating tag

BO submit namespace when creating tag

--- app/assets/javascripts/backoffice/views/election/tags_view.js.coffee
+++ app/assets/javascripts/backoffice/views/election/tags_view.js.coffee
@@ -67,7 +67,7 @@
         @
       tag.fetch()
     else # create
-      tag_name = tag_id = $('.tag-name', form).val()
+      tagName = tag_id = $('.tag-name', form).val()
       tag = new TagModel()
       tag.bind 'error',
         (tag, response) ->
@@ -76,7 +76,8 @@
         @
       election = @election
       parent_tag = @tag if @tag
-      tag.save {name: tag_name},
+      tagNamespace = tagName.replace /\s+/g, '-'
+      tag.save {name: tagName, namespace: tagNamespace},
         success: (tag) -> # link tag to election
           if parent_tag?
             election.addTag tag, parent_tag


Client: Sidebar: Feed: Deleted showPopularMessages prop

--- client/activity/lib/components/channelthreadpane/index.coffee
+++ client/activity/lib/components/channelthreadpane/index.coffee
@@ -23,7 +23,6 @@
       channelThread       : getters.selectedChannelThread
       popularChannels     : getters.popularChannels
       channelParticipants : getters.selectedChannelParticipants
-      showPopularMessages : getters.showPopularMessagesFlag
     }

@@ -82,8 +81,7 @@

         <PublicFeedPane
           ref='pane'
-          thread={thread}
-          showPopularMessages={@state.showPopularMessages} />
+          thread={thread} />

     else


Less fat arrows in the StorageManager

Less fat arrows in the StorageManager

--- vendor/assets/javascripts/brainstem/storage-manager.coffee
+++ vendor/assets/javascripts/brainstem/storage-manager.coffee
@@ -23,34 +23,34 @@

   # Access the cache for a particular collection.
   #    manager.storage("time_entries").get(12).get("title")
-  storage: (name) =>
+  storage: (name) ->
     @getCollectionDetails(name).storage

-  dataUsage: =>
+  dataUsage: ->
     sum = 0
     for dataType in @collectionNames()
       sum += @storage(dataType).length
     sum

-  reset: =>
+  reset: ->
     for name, attributes of @collections
       attributes.storage.reset []
       attributes.cache = {}

   # Access details of a collection.  An error will be thrown if the collection cannot be found.
-  getCollectionDetails: (name) =>
+  getCollectionDetails: (name) ->
     @collections[name] || @collectionError(name)

-  collectionNames: =>
+  collectionNames: ->
     _.keys(@collections)

-  collectionExists: (name) =>
+  collectionExists: (name) ->
     !!@collections[name]

-  setErrorInterceptor: (interceptor) =>
+  setErrorInterceptor: (interceptor) ->
     @errorInterceptor = interceptor || (handler, modelOrCollection, options, jqXHR, requestParams) -> handler?(jqXHR)

-  loadModel: (name, id, options = {}) =>
+  loadModel: (name, id, options = {}) ->
     return if not id

     successCallback = options.success
@@ -60,7 +60,7 @@
     ml.done(successCallback) if successCallback? && _.isFunction(successCallback)
     ml.externalObject

-  loadCollection: (name, options = {}) =>
+  loadCollection: (name, options = {}) ->
     successCallback = options.success
     options = _.omit(options, 'success')

@@ -68,22 +68,22 @@
     cl.done(successCallback) if successCallback? && _.isFunction(successCallback)
     cl.externalObject

-  collectionError: (name) =>
+  collectionError: (name) ->
     Brainstem.Utils.throwError("Unknown collection #{name} in StorageManager.  Known collections: #{_(@collections).keys().join(", ")}")

-  createNewCollection: (collectionName, models = [], options = {}) =>
+  createNewCollection: (collectionName, models = [], options = {}) ->
     loaded = options.loaded
     delete options.loaded
     collection = new (@getCollectionDetails(collectionName).klass)(models, options)
     collection.setLoaded(true, trigger: false) if loaded
     collection

-  createNewModel: (modelName, options) =>
+  createNewModel: (modelName, options) ->
     new (@getCollectionDetails(modelName.pluralize()).modelKlass)(options || {})

   # Expectations and stubbing

-  stub: (collectionName, options) =>
+  stub: (collectionName, options) ->
     if @expectations?
       expectation = new Brainstem.Expectation(collectionName, options, @)
       @expectations.push expectation
@@ -91,13 +91,13 @@
     else
       throw "You must call #enableExpectations on your instance of Brainstem.StorageManager before you can set expectations."

-  stubImmediate: (collectionName, options) =>
+  stubImmediate: (collectionName, options) ->
     @stub collectionName, $.extend({}, options, immediate: true)

-  enableExpectations: =>
+  enableExpectations: ->
     @expectations = []

-  handleExpectations: (loader) =>
+  handleExpectations: (loader) ->
     name = loader.getCollectionName()

     for expectation in @expectations


Socials: clear cache when there is an err

--- workers/social/lib/social/models/socialapi/requests.coffee
+++ workers/social/lib/social/models/socialapi/requests.coffee
@@ -34,6 +34,7 @@
   (err, response, body) ->
     if err
       if err.code is "ECONNREFUSED"
+        SOCIAL_API_URLS = []
         return callback {message: "Social API is currently under maintenance"}
       return callback err


Stacks: Shorten copy.

Stacks: Shorten copy.

--- client/Environments/views/firewall/firewallfilterformview.coffee
+++ client/Environments/views/firewall/firewallfilterformview.coffee
@@ -9,10 +9,10 @@
         cssClass       : "half"
         itemClass      : KDSelectBox
         selectOptions  : [
-          { title      : "IP"                 ,  value : "ip"             }
-          { title      : "Country"            ,  value : "country"        }
-          { title      : "Request per second" ,  value : "request.second" }
-          { title      : "Request per minute" ,  value : "request.minute" }
+          { title      : "IP"       ,  value : "ip"             }
+          { title      : "Country"  ,  value : "country"        }
+          { title      : "Req./sec" ,  value : "request.second" }
+          { title      : "Req./min" ,  value : "request.minute" }
         ]
         validate       :
           rules        :


home: use generic class names in stacktemplateitem

--- client/home/lib/stacks/components/stacktemplateitem/index.coffee
+++ client/home/lib/stacks/components/stacktemplateitem/index.coffee
@@ -4,16 +4,16 @@

 module.exports = StackTemplateItem = ({ template }) ->

-

+

       {template.get 'title'}

-

+

       Last updated <TimeAgo from={template.getIn ['meta', 'modifiedAt']} />

-

+

       RE-INITIALIZE
       LAUNCH


Fix logic for fresh activity comments..

Fix logic for fresh activity comments..

--- client/landing/app/CommonViews/comments/commentviewheader.coffee
+++ client/landing/app/CommonViews/comments/commentviewheader.coffee
@@ -58,7 +58,8 @@

     # Get correct number of items in list from controller
     # I'm not sure maybe its not a good idea
-    @onListCount = @parent.commentController.getItemCount()
+    if @parent.commentController.getItemCount()
+      @onListCount = @parent.commentController.getItemCount()

     _newCount = @getData().repliesCount

@@ -72,10 +73,8 @@
     # if nothing changed it means user clicked like button
     # so we don't need to touch anything
     if _newCount > @oldCount
-      # log "ITEM ADDED"
       @newCount++
     else if _newCount < @oldCount
-      # log "ITEM DELETED"
       if @newCount > 0 then @newCount--

     # If the count is changed then we need to update UI
@@ -88,17 +87,20 @@
   updateNewCount:->

     # If there is no comments so we can not have new comments
-    if @oldCount == 0 then @newCount = 0
+    if @oldCount is 0 then @newCount = 0

     # If we have comments more than 0 we should show the new item link
-    if @newCount > 0 and @newCount isnt @oldCount
+    if @newCount > 0
       @show()
       @newItemsLink.updatePartial "#{@newCount} new"
       @newItemsLink.setClass('in')
     else
       @newItemsLink.unsetClass('in')
-      if @onListCount is @oldCount
-        @hide
+
+    if @onListCount is @oldCount
+      @hide()
+    else
+      @show()

   hide:->
     @$().slideUp 150


Get rid of unwanted margin by using div rather than p

--- core/app/backbone/views/react_install_extension_button.coffee
+++ core/app/backbone/views/react_install_extension_button.coffee
@@ -38,7 +38,7 @@
               "button-success", "button-huge",
               onclick: ((e)-> e.preventDefault())],
             _span [style: display: 'none'], 'Factlink'
-        _p [],
+        _div [],
           _em [],
             'To install Factlink, drag this button to your bookmarks bar'
     else if @props.huge_button && extension_installed


pause and resume game on windows blur and focus events

--- assets/js/app.coffee
+++ assets/js/app.coffee
@@ -395,6 +395,8 @@
         return false
     , 'json'

+
+
   # When load the app we init VK JSapi
   # this callback gets the score of current user and sets
   # some useful variables to interact with vk
@@ -402,6 +404,16 @@
   onVkInitSuccess = (data) ->
     uid = document.location.search.match(/user_id=\d+/)[0].slice 8
     app_id = document.location.search.match(/api_id=\d+/)[0].slice 7
+
+
+    VK.addCallback 'onWindowBlur', ->
+      console.log 'Pause game'
+      pauseScreen()
+
+    VK.addCallback 'onWindowFocus', ->
+      console.log 'Resume game'
+      removePauseScreen()
+
     VK.api 'storage.get', {key: 'score'}, (data) ->
       if data.response
         if data.response == ''


fixed audio singleton

fixed audio singleton

--- app/assets/coffee/apps/episodes/player/player_controller.coffee
+++ app/assets/coffee/apps/episodes/player/player_controller.coffee
@@ -4,216 +4,230 @@
     Player.Controller = showControls: ->
       require ["entities/player"], ->
           self = this
-          playerData = new Swabcast.Entities.PlayerData()
-          # fetchPlayerData = Swabcast.request("player:savedata")
-          # $.when(fetchPlayerData).done (playerData) ->
-          self = this
-          @initializePlayer = ->
-            player = self.audioPlayer.getInstance()
-            playerData = self.defaultPlayerState()
-            player.createAudio() #create audio
-            self.updateAudio()
-
-          @updateAudio = (source, options) ->
-            player = self.audioPlayer.getInstance()
-            opts = options or {}
-            if playerData
-              opts.currentPosition = playerData.get("currentPosition") or 0
-
-            if source and source != ""
-              player.resetAudio source
-              player.setAudioSource source
-              player.setPosition = options.currentPosition
-              player.audio.load()
-
-          @removeCurrentAudio = ->
-            player = self.audioPlayer.getInstance()
-            player.clearAudio()
-
-          @newPlayerData = (newModelData) ->
-            newPlayerData = new Swabcast.Entities.PlayerData(
-              albumArt: newModelData.get("albumArt")
-              mediaUrl: newModelData.get("mediaUrl")
-              title: newModelData.get("episodeTitle")
-              currentPosition: newModelData.get("currentPosition") or 0
-            )
-            newPlayerData.save()
-            newPlayerData
-
-          @playNow = (newModelData) ->
-            player = self.audioPlayer.getInstance()
-            self.newPlayerData newModelData
-            player.play()
-
-          @defaultPlayerState = ->
-            defaultData = new Swabcast.Entities.PlayerData(
-              id:0
-              albumArt: "default.jpg"
-              mediaUrl: ""
-              title: ""
-            )
-            defaultData.save()
-            defaultData
-
-          #audioplayer object
-          @audioPlayer = (->
-
-            init = ->
-              createAudio: ->
-                @audio = new Audio()
-                @state = "disabled"
-
-              resetAudio: () ->
-                @audio.remove()
-                @audio = new Audio()
-                @audio.src = ""
-
-              play: ->
-                @audio.play() if @state is "ready"
-                @state = "playing"
-
-              pause: ->
-                @audio.pause()
-                playerData.set("currentPosition", @audio.currentTime)
-                playerData.save()
-                @state = "ready"
-
-              getState: ->
-                return @state
-
-              setState: (newState) ->
-                return  unless newState
-                @state = newState
-
-              setAudioSource: (mediaUrl) ->
-                @audio.src = mediaUrl
-                return  if not mediaUrl or typeof mediaUrl isnt "string"
-                @state = "ready"
-
-              getAudioSource: ->
-                return @audio.src
-
-              clearAudio: ->
-                if @state == "playing"
+          fetchPlayerData = Swabcast.request("player:savedata")
+          $.when(fetchPlayerData).done (playerData) ->
+            self = this
+            if !playerData or playerData.invalid()
+              playerData = new Swabcast.Entities.PlayerData()
+
+            #audioplayer singleton
+            AudioPlayer = (->
+
+              init = ->
+                if !@audio
+                  @audio = new Audio()
+                if !@state
+                  @state = "disabled"
+
+                # createAudio: ->
+                #   @audio = new Audio()
+                #   @state = "disabled"
+
+                resetAudio: () ->
+                  if @audio
+                    @audio.remove()
+                  @audio = new Audio()
+                  @audio.src = ""
+
+                play: ->
+                  @audio.play() if @state is "ready"
+                  @state = "playing"
+
+                pause: ->
                   @audio.pause()
                   playerData.set("currentPosition", @audio.currentTime)
-                delete @audio
-                @audio = new Audio()
-                # @audio.load()
-                @state = "disabled"
-
-              # setAudioOptions: (options) ->
-
-              setPosition: (time) ->
-                @audio.currentTime = time  if typeof episodePosition is "number"
-
-              skipback: ->
-                if (@state is "ready" or @state is "playing") and @audio.currentTime > 10
-                  @audio.pause()
-                  @audio.currentTime = (@audio.currentTime - 10)
-                  @audio.play()
-
-              skipahead: ->
-                self = this
-                if (self.state is "ready" or self.state is "playing") and self.audio.currentTime + 10 <= self.audio.duration
-                  self.audio.pause()
-                  self.audio.currentTime = (self.audio.currentTime + 10)
-                  self.audio.play()
-
-              currentMediaUrl: ->
-                @audio.src
-
-            instance = undefined
-
-            getInstance: ->
-              instance = init()  unless instance
-              instance
-          )()
-
-          @initializePlayer()
-          @playerControls = new View.Player(model: playerData)
-          @playerControls.on "episode:playpause", ->
-            switch self.audioPlayer.state
-              when "disabled", "ready"
-                self.audioPlayer.play()
-                $("#play-icon").addClass "Hidden"
-                $("#pause-icon").removeClass "Hidden"
-                self.audioPlayer.setState "playing"
-              when "playing"
-                self.audioPlayer.pause()
-                playerData.set currentPosition: self.audioPlayer.audio.currentTime
-                $("#play-icon").removeClass "Hidden"
-                $("#pause-icon").addClass "Hidden"
-                self.audioPlayer.setState "ready"
-              else
-
-          @playerControls.listenTo Player, "playlist:removed", (deleted) ->
-            if deleted.has("mediaUrl") and deleted.get("mediaUrl") is self.audioPlayer.audio.src
-              self.audioPlayer.audio.pause()
-              self.audioPlayer.audio.src = null
-            self.updatePlayer()
-
-          @playerControls.on "episode:skipback", ->
-            self.audioPlayer.skipback()
-
-          @playerControls.on "episode:skipahead", ->
-            self.audioPlayer.skipahead()
-
-          # TODO - this should be seperated as an API
-          Swabcast.commands.setHandlers
-            "player:empty": ->
-              sourceUrl = ""
-              # player commands
-              self.playerControls.model.destroy()
-              self.playerControls.model = self.defaultPlayerState()
-              self.updateAudio sourceUrl
-              self.playerControls.render()
-              playerData.save()
-
-            "player:playnow": (uuid) ->
-              require ["entities/feed"], ->
-                getEpisode = Swabcast.request("entity:episode", uuid)
-                player = self.audioPlayer.getInstance()
-                $.when(getEpisode).done (episodeModel) ->
-                  # audio options
-                  options = {}
-                  options.preload = true
-
-                  # player commands
-                  self.playerControls.model.destroy()
-                  self.playerControls.model = self.newPlayerData(episodeModel)
-                  # self.newPlayerData episodeModel
-                  self.updateAudio episodeModel.get("mediaUrl"), options
-                  self.playerControls.render()
+                  playerData.save()
+                  @state = "ready"
+
+                getState: ->
+                  return @state
+
+                setState: (newState) ->
+                  return  unless newState
+                  @state = newState
+
+                setAudioSource: (mediaUrl) ->
+                  @audio.src = mediaUrl
+                  return  if not mediaUrl or typeof mediaUrl isnt "string"
+                  @state = "ready"
+
+                getAudioSource: ->
+                  return @audio.src
+
+                clearAudio: ->
+                  if @state == "playing"
+                    @audio.pause()
+                    playerData.set("currentPosition", @audio.currentTime)
+                  delete @audio
+                  @audio = new Audio()
+                  # @audio.load()
+                  @state = "disabled"
+
+                # setAudioOptions: (options) ->
+
+                setPosition: (time) ->
+                  @audio.currentTime = time  if typeof episodePosition is "number"
+
+                skipback: ->
+                  if (@state is "ready" or @state is "playing") and @audio.currentTime > 10
+                    @audio.pause()
+                    @audio.currentTime = (@audio.currentTime - 10)
+                    @audio.play()
+
+                skipahead: ->
+                  self = this
+                  if (self.state is "ready" or self.state is "playing") and self.audio.currentTime + 10 <= self.audio.duration
+                    self.audio.pause()
+                    self.audio.currentTime = (self.audio.currentTime + 10)
+                    self.audio.play()
+
+                currentMediaUrl: ->
+                  @audio.src
+
+              instance = undefined
+
+              getInstance: ->
+                instance = init()  unless instance
+                instance
+            )()
+
+            @initializePlayer = ->
+              player = new AudioPlayer.getInstance()
+              playerData = self.defaultPlayerState()
+              # player.createAudio() #create audio
+              self.updateAudio()
+
+            @updateAudio = (source, options) ->
+              player = AudioPlayer.getInstance()
+              opts = options or {}
+              if playerData
+                opts.currentPosition = playerData.get("currentPosition") or 0
+
+              if source and source != ""
+                player.resetAudio source
+                player.setAudioSource source
+                player.setPosition = options.currentPosition
+                player.audio.load()
+
+            @removeCurrentAudio = ->
+              player = AudioPlayer.getInstance()
+              player.clearAudio()
+
+            @newPlayerData = (newModelData) ->
+              newPlayerData = new Swabcast.Entities.PlayerData(
+                albumArt: newModelData.get("albumArt")
+                mediaUrl: newModelData.get("mediaUrl")
+                title: newModelData.get("episodeTitle")
+                currentPosition: newModelData.get("currentPosition") or 0
+              )
+              newPlayerData.save()
+              newPlayerData
+
+            @playNow = (newModelData) ->
+              player = AudioPlayer.getInstance()
+              self.newPlayerData newModelData
+              player.play()
+
+            @defaultPlayerState = ->
+              defaultData = new Swabcast.Entities.PlayerData(
+                id:0
+                albumArt: "default.jpg"
+                mediaUrl: ""
+                title: ""
+              )
+              defaultData.save()
+              defaultData
+
+
+
+            @initializePlayer()
+            @playerControls = new View.Player(model: playerData)
+            @playerControls.on "episode:playpause", ->
+              player = AudioPlayer.getInstance()
+              switch player.state
+                when "disabled", "ready"
                   player.play()
-                  playerData.save()
-
-            "player:setepisode": (episodeId) ->
-              # audio options
-              options = {}
-              options.preload = true
-              require ["entities/playlist"], ->
-
-                fetchEpisode = Swabcast.request("playlist:episode", episodeId)
-                $.when(fetchEpisode).done (episodeModel) ->
-                  self.playerControls.model.destroy()
-                  self.playerControls.model = self.newPlayerData(episodeModel)
-                  self.updateAudio episodeModel.get("mediaUrl"), options
-                  self.playerControls.render()
-                  playerData.save()
-
-            "playlist:updatenowplaying": (episodeModel) ->
-              # audio options
-              options = {}
-              options.preload = true
-
-              # player commands
-              self.playerControls.model.destroy()
-              self.playerControls.model = self.newPlayerData(episodeModel)
-              self.updateAudio episodeModel.get("mediaUrl"), options
-              self.playerControls.render()
-              playerData.save()
-
-          Swabcast.playerRegion.show @playerControls
+                  $("#play-icon").addClass "Hidden"
+                  $("#pause-icon").removeClass "Hidden"
+                  player.setState "playing"
+                when "playing"
+                  player.pause()
+                  playerData.set currentPosition: player.audio.currentTime
+                  $("#play-icon").removeClass "Hidden"
+                  $("#pause-icon").addClass "Hidden"
+                  player.setState "ready"
+                else
+
+            @playerControls.listenTo Player, "playlist:removed", (deleted) ->
+              if deleted.has("mediaUrl") and deleted.get("mediaUrl") is player.audio.src
+                player.audio.pause()
+                player.audio.src = null
+              self.updatePlayer()
+
+            @playerControls.on "episode:skipback", ->
+              player = AudioPlayer.getInstance()
+              player.skipback()
+
+            @playerControls.on "episode:skipahead", ->
+              player = AudioPlayer.getInstance()
+              player.skipahead()
+
+            # TODO - this should be seperated as an API
+            Swabcast.commands.setHandlers
+              "player:empty": ->
+                sourceUrl = ""
+                # player commands
+                self.playerControls.model.destroy()
+                self.playerControls.model = self.defaultPlayerState()
+                self.updateAudio sourceUrl
+                self.playerControls.render()
+                playerData.save()
+
+              "player:playnow": (uuid) ->
+                require ["entities/feed"], ->
+                  getEpisode = Swabcast.request("entity:episode", uuid)
+                  player = AudioPlayer.getInstance()
+                  $.when(getEpisode).done (episodeModel) ->
+                    # audio options
+                    options = {}
+                    options.preload = true
+
+                    # player commands
+                    self.playerControls.model.destroy()
+                    self.playerControls.model = self.newPlayerData(episodeModel)
+                    # self.newPlayerData episodeModel
+                    self.updateAudio episodeModel.get("mediaUrl"), options
+                    self.playerControls.render()
+                    player.play()
+                    playerData.save()
+
+              "player:setepisode": (episodeId) ->
+                # audio options
+                options = {}
+                options.preload = true
+                require ["entities/playlist"], ->
+                  player = AudioPlayer.getInstance()
+                  fetchEpisode = Swabcast.request("playlist:episode", episodeId)
+                  $.when(fetchEpisode).done (episodeModel) ->
+                    console.log(episodeModel)
+                    self.playerControls.model.destroy()
+                    self.playerControls.model = self.newPlayerData(episodeModel)
+                    self.updateAudio episodeModel.get("mediaUrl"), options
+                    self.playerControls.render()
+                    playerData.save()
+
+              "playlist:updatenowplaying": (episodeModel) ->
+                # audio options
+                options = {}
+                options.preload = true
+
+                # player commands
+                self.playerControls.model.destroy()
+                self.playerControls.model = self.newPlayerData(episodeModel)
+                self.updateAudio episodeModel.get("mediaUrl"), options
+                self.playerControls.render()
+                playerData.save()
+
+            Swabcast.playerRegion.show @playerControls

   Swabcast.EpisodesApp.Player.Controller


Skip time on date input

Skip time on date input

--- app/assets/javascripts/ab_admin/inputs/datetime_input.js.coffee
+++ app/assets/javascripts/ab_admin/inputs/datetime_input.js.coffee
@@ -6,10 +6,10 @@
       todayBtn: true
       language: I18n.locale

-    search_options = _.defaults({pickerPosition: "bottom-left datetimepicker-bottom-left-custom"}, base_options)
-    $('#search_form input.datepicker').datetimepicker search_options
+    search_form_options = _.defaults({pickerPosition: "bottom-left datetimepicker-bottom-left-custom"}, base_options)
+    $('#search_form input.datepicker').datetimepicker search_form_options

-    date_picker_options = _.defaults({format: "dd.mm.yyyy"}, base_options)
+    date_picker_options = _.defaults({format: "dd.mm.yyyy", minView: 2}, base_options)
     $('.simple_form input.date_picker').datetimepicker date_picker_options

     $('.simple_form input.datetime_picker').datetimepicker base_options


tabs show subviews on click

tabs show subviews on click

--- app/assets/javascripts/admin/views/task/task_create_view.coffee
+++ app/assets/javascripts/admin/views/task/task_create_view.coffee
@@ -2,28 +2,80 @@
 underscore : _
 backbone.marionette : marionette
 routes : routes
+admin/views/task/task_create_from_form_view : TaskCreateFromFormView
+admin/views/task/task_create_from_nml_view : TaskCreateFromNMLView
+admin/views/task/task_create_bulk_import_view : TaskCreateBulkImportView
 ###

 class TaskCreateView extends Backbone.Marionette.LayoutView

+  # keep track of the active view
+  activeView : null
+
   id : "task-create"
   className : "container wide task-type-administration"
   template : _.template("""
-

+

+

-            Create Task
+            Create Task

-            Create Task from IML File
+            Create Task from NML File

             Import Task in Bulk

-

-

-

-

+

+

+

+
+

   """)
+
+  regions:
+      # content region for tabs
+      tabPane: ".tab-pane"
+
+  events:
+      # trigger tab changes
+      "click #tab-createFromForm" : "showTabCreateFromForm"
+      "click #tab-createFromNML" : "showTabCreateFromNML"
+      "click #tab-createBulkImport" : "showTabCreateBulkImport"
+
+  # change to activated tabs
+
+  ###*
+   * Activate Default Form View
+   *
+   * @method showTabCreateFromForm
+   ###
+  showTabCreateFromForm: ->
+
+      @activeView = new TaskCreateFromFormView()
+      @tabPane.show(@activeView)
+
+
+  ###*
+   * Activate NML Form View
+   *
+   * @method showTabCreateFromNML
+   ###
+  showTabCreateFromNML: ->
+
+    @activeView = new TaskCreateFromNMLView()
+    @tabPane.show(@activeView)
+
+
+  ###*
+   * Activate Bulk Import View
+   *
+   * @method showTabCreateBulkImport
+   ###
+  showTabCreateBulkImport: ->
+
+    @activeView = new TaskCreateBulkImportView()
+    @tabPane.show(@activeView)


[#59] Corrige bug no filtro por ano do treemap

--- app/assets/javascripts/angular_app/directives/treemap.js.coffee
+++ app/assets/javascripts/angular_app/directives/treemap.js.coffee
@@ -4,7 +4,7 @@
   buildGraph = (element, drilldowns, year) ->
     state =
       drilldowns: drilldowns
-      year: year
+      cut: "time.year:#{year}"
     context =
       dataset: openspending.dataset
       siteUrl: openspending.url


support button style subscription choices

--- app/assets/javascripts/effective_orders/subscriptions.js.coffee
+++ app/assets/javascripts/effective_orders/subscriptions.js.coffee
@@ -14,15 +14,21 @@
         form.addClass('stripe-success').submit() # Submits the form. As this is a remote form, submits via JS

 # Hijack submit and get a stripe token
-$(document).on 'click', ".effective-orders-stripe-token-required[type='submit']", (event) ->
-  $form = $(event.currentTarget).closest('form')
+$(document).on 'click', ".effective-orders-stripe-token-required[type='submit'],[data-choose-stripe-plan-id]", (event) ->
+  $obj = $(event.currentTarget)
+  $form = $obj.closest('form')

   # Get stripe data payload
   stripe = $form.data('stripe')
   return unless stripe?

+  # If we're doing choose button mode
+  if $obj.data('choose-stripe-plan-id')
+    $form.find("input[name$='[stripe_plan_id]']").val($obj.data('choose-stripe-plan-id'))
+    return true unless $obj.hasClass('effective-orders-stripe-token-required')
+
   # Make sure there is a plan selected
-  selected_plan_id = $form.find("input[name$='[stripe_plan_id]']:checked").val() || ''
+  selected_plan_id = $form.find("input[name$='[stripe_plan_id]']:checked").val() || $form.find("input[name$='[stripe_plan_id]']").val() || ''
   return unless selected_plan_id.length > 0

   # Match plan


support other route

support other route

--- apps/fair/components/browse/router.coffee
+++ apps/fair/components/browse/router.coffee
@@ -13,6 +13,7 @@
     ':id/browse/booths(/)': 'booths'
     ':id/browse/booths/region/:region(/)': 'boothsRegion'
     ':id/browse/booths/section/:section(/)': 'boothsSection'
+    ':id/browse/shows/section=:section(/)': 'boothsSection'
     ':id/browse/category/:category(/)': 'category'
     ':id/browse/exhibitors(/)': 'exhibitors'
     ':id/browse(/)': 'browse'


as requested, i added a condition, if the window matches max-width 500 it will close, otherwise no

--- packages/rocketchat-ui-flextab/flex-tab/tabs/userInfo.coffee
+++ packages/rocketchat-ui-flextab/flex-tab/tabs/userInfo.coffee
@@ -124,7 +124,7 @@
                 if window.matchMedia("(max-width: 500px)").matches
                     FlowRouter.go('direct', { username: @username }, FlowRouter.current().queryParams, RocketChat.TabBar.closeFlex())
                 else
-				    FlowRouter.go('direct', { username: @username }, FlowRouter.current().queryParams)
+                    FlowRouter.go('direct', { username: @username }, FlowRouter.current().queryParams)

 	"click .flex-tab  .video-remote" : (e) ->
 		if RocketChat.TabBar.isFlexOpen()


Fix routes

Fix routes

--- app/routes.coffee
+++ app/routes.coffee
@@ -1,4 +1,4 @@
 'use strict'

 module.exports = (match) ->
-  match '', 'application#index'
+  match '', 'app#index'


Build Anchor Tab using i18n information.

--- src/main/webapp/coffeescripts-hidden/load.coffee
+++ src/main/webapp/coffeescripts-hidden/load.coffee
@@ -20,6 +20,9 @@
 resourcesDomain = anchortab?.resourcesDomain || parser.hostname
 secureResourcesDomain = anchortab?.secureResourcesDomain || parser.hostname
 jqVersion = "1.9.1"
+
+window.anchortab = window.anchortab || {}
+window.anchortab.i18n = {}

 ##
 ## LIB FUNCTIONS
@@ -140,7 +143,7 @@
   unless email.match(/.+@.+\..+/)
     $("#anchor-tab")
       .find(".success-message")
-        .text("That doesn't look like a valid email address. Please check for typos and try again.")
+        .text(anchortab.i18n["tab-invalidEmail"])
       .end()
       .addClass("success")

@@ -190,7 +193,7 @@

     error: (xhr, status, error) ->
       $("#anchor-tab [disabled]").removeAttr("disabled")
-      alert("Something went wrong submitting your email. Please contact us if you continue to see this error.")
+      alert(anchortab.i18n["tab-somethingWentWrong"])

       gaqInfo = "Domain: " + document.domain + " Response Status: " + status
       _gaq.push ["at._trackEvent", "Submission", "Submission Error", gaqInfo]
@@ -205,6 +208,7 @@
   colorScheme = tabJson.colorScheme
   customMessage = tabJson.customText
   collectName = tabJson.collectName
+  window.anchortab.i18n = tabJson.i18n

   # Load the Anchor Tab stylesheet.
   atStyleSheet =
@@ -251,18 +255,18 @@
         $("<input />")
           .addClass('email-input')
           .attr('type', 'text')
-          .attr('placeholder', "email address")
+          .attr('placeholder', anchortab.i18n["tab-emailAddress"])
       )
       .append(
         $("<button />")
           .addClass('email-submission')
-          .text("Submit")
+          .text(anchortab.i18n["tab-submit"])
           .click(submitEmail)
       )
       .append(
         $("<button />")
           .addClass('minimize')
-          .text("Minimize Anchor Tab")
+          .text(anchortab.i18n["tab-minimizeAnchorTab"])
           .click(->
               $("#anchor-tab").removeClass("visible").addClass("minimized")
               setStateCookie 'minimized'
@@ -274,7 +278,7 @@
       .append(
         $("<button />")
           .addClass("maximize")
-          .text("Maximize Anchor Tab")
+          .text(anchortab.i18n["tab-maximizeAnchorTab"])
           .click(->
               $("#anchor-tab").removeClass("minimized").addClass("visible")
               setStateCookie 'visible'
@@ -299,7 +303,7 @@
       $("<input />")
         .addClass("first-name-input")
         .attr("type", "text")
-        .attr("placeholder", "first name")
+        .attr("placeholder", anchortab.i18n["tab-firstName"])
     )

   if isBrowserIe()


added ball source by default

added ball source by default

--- src/main.coffee
+++ src/main.coffee
@@ -4,7 +4,44 @@
 converter = new Markdown.Converter()
 markdown = (s) -> converter.makeHtml(s)

-code = '# Headline\n\nThis is some plain text.\n\n    print("Hello World");'
+code = """
+
+# Bouncing ball
+
+Gravity:
+
+    var gravity = +slider(0, 9.81);
+
+Starting Height:
+
+    var start = +slider(0, 100, 1);
+
+Drag:
+
+    var drag = 1 - +slider(0, 0.1, 0.0001);
+
+Bounce factor:
+
+    var bounce = +slider();
+
+    var height = [], velocity = [], v = 0, h = start;
+    for (var i = 0; i < 100; i++) {
+      v -= gravity;
+        v *= drag;
+        h += v;
+        if (h < 0) { v = -v * bounce; h = 0; }
+        velocity.push(v);
+        height.push(h);
+    }
+
+Height:
+
+    plotseries(height);
+
+Velocity:
+
+    plotseries(velocity);"""
+
 textarea = undefined
 outputarea = undefined
 showCodeButton = undefined


Taxon tree: Use this.el

Taxon tree: Use this.el

--- backend/app/assets/javascripts/spree/backend/taxonomy.js.coffee
+++ backend/app/assets/javascripts/spree/backend/taxonomy.js.coffee
@@ -33,7 +33,7 @@

   draw_tree: (taxonomy) ->
     taxons_template = HandlebarsTemplates["taxons/tree"]
-    $('#taxonomy_tree')
+    this.$el
       .html( taxons_template({ taxons: [taxonomy.root] }) )
       .find('ul')
       .sortable
@@ -96,7 +96,7 @@
   }

   initialize: ({taxonomy_id}) ->
-    _.bindAll(this, 'redraw_tree', 'handle_create')
+    _.bindAll(this, 'redraw_tree', 'draw_tree', 'handle_create')
     $('.add-taxon-button').on('click', @handle_create)

     this.taxonomy_id = taxonomy_id


column select: prevent enter from bubbling

--- app/assets/javascripts/app/lib/app_post/column_select.coffee
+++ app/assets/javascripts/app/lib/app_post/column_select.coffee
@@ -90,6 +90,9 @@

   onFilterKeydown: (event) ->
     return if event.keyCode != 13
+
+    event.stopPropagation()
+    event.preventDefault()

     firstVisibleOption = @poolOptions.not('.is-filtered').not('.is-hidden').first()
     if firstVisibleOption


StackEditor: Use soft tabs.

StackEditor: Use soft tabs.

--- client/admin/lib/views/stacks/editors/basestackeditorview.coffee
+++ client/admin/lib/views/stacks/editors/basestackeditorview.coffee
@@ -41,6 +41,7 @@
       ace.setTabSize 2, no
       ace.setShowPrintMargin no, no
       ace.setEnableAutocomplete no, no
+      ace.setUseSoftTabs yes, no

       kd.utils.defer =>
         @getEditorSession().setScrollTop 0


Remove dangling method

Remove dangling method

--- client/app/CommonViews/comments/commentlistviewcontroller.coffee
+++ client/app/CommonViews/comments/commentlistviewcontroller.coffee
@@ -62,14 +62,6 @@
             listView.emit "OwnCommentHasArrived"
           listView.emit "BackgroundActivityFinished"

-  fetchCommentsByRange:(from,to,callback)=>
-    [to,callback] = [callback,to] unless callback
-    query = {from,to}
-    message = @getListView().getData()
-    message.commentsByRange query,(err,comments)=>
-      @getListView().emit "BackgroundActivityFinished"
-      callback err,comments
-
   fetchAllComments:(skipCount=3, callback = noop)=>

     listView = @getListView()


Workspace: Switched terminal classes to brand new, awesome SharableTerminal classes.

--- client/app/CommonViews/workspace/panel/collaborativepanel.coffee
+++ client/app/CommonViews/workspace/panel/collaborativepanel.coffee
@@ -41,7 +41,7 @@

     if isJoinedASession
       if paneOptions.type is "terminal"
-        PaneClass = CollaborativeClientTerminalPane
+        PaneClass = SharableClientTerminalPane
       else if paneOptions.type is "finder"
         PaneClass = CollaborativeClientFinderPane

@@ -53,7 +53,7 @@
     @emit "NewPaneCreated", pane

 CollaborativePanel::EditorPaneClass        = CollaborativeEditorPane
-CollaborativePanel::TerminalPaneClass      = CollaborativeTerminalPane
+CollaborativePanel::TerminalPaneClass      = SharableTerminalPane
 CollaborativePanel::FinderPaneClass        = CollaborativeFinderPane
 CollaborativePanel::TabbedEditorPaneClass  = CollaborativeTabbedEditorPane
 CollaborativePanel::VideoPaneClass         = VideoPane


Override extractArray in TaskSerializer.

--- app/assets/javascripts/serializers/task_serializer.js.coffee
+++ app/assets/javascripts/serializers/task_serializer.js.coffee
@@ -21,6 +21,18 @@
   coerceId: (id) ->
     (if not id? then null else id + "")

+  normalizeType: (hash) ->
+    if hash.type
+      hash.qualified_type = hash.type
+      hash.type = hash.type.replace(/.+::/, '')
+    hash
+
+  extractTypeName: (prop, hash) ->
+    if hash.type
+      @typeForRoot hash.type
+    else
+      @typeForRoot prop
+
   # This is overridden because finding a 'task' and getting back a root key of 'author_task' will
   # break the isPrimary check.
   extractSingle: (store, primaryType, payload, recordId, requestType) ->
@@ -38,15 +50,10 @@

       #jshint loopfunc:true
       for hash in payload[prop]
-        # hash.foobar = 'hello!'
-        # custom code starts here
-        typeName = if hash.type
-          hash.qualified_type = hash.type
-          hash.type = hash.type.replace(/.+::/, '')
-          @typeForRoot hash.type
-        else
-          @typeForRoot prop
-        # custom code ends here
+        # ===========> custom code for namespacing and sti starts here
+        hash = @normalizeType(hash)
+        typeName = @extractTypeName(prop, hash)
+        # <=========== custom code for namespacing and sti ends here
         type = store.modelFor(typeName)
         typeSerializer = store.serializerFor(type)
         hash = typeSerializer.normalize(type, hash, prop)
@@ -66,6 +73,39 @@

     primaryRecord

+  extractArray: (store, primaryType, payload) ->
+    payload = @normalizePayload(primaryType, payload)
+    # primaryTypeName = primaryType.typeKey
+    primaryTypeName = 'task' # we'll always look for tasks at the root.
+    primaryArray = undefined
+    for prop of payload
+      typeKey = prop
+      forcedSecondary = false
+      if prop.charAt(0) is "_"
+        forcedSecondary = true
+        typeKey = prop.substr(1)
+      typeName = @typeForRoot(typeKey)
+      type = store.modelFor(typeName)
+      arrayTypeSerializer = store.serializerFor(type) # cache the serializer based on the array's type key
+      isPrimary = (not forcedSecondary and (type.typeKey is primaryTypeName))
+
+      #jshint loopfunc:true
+      normalizedArray = Ember.ArrayPolyfills.map.call(payload[prop], (hash) ->
+        # =======> Each item in the array of tasks could have a different type.
+        hash = @normalizeType(hash)
+        if hash.type
+          itemSerializer = store.serializerFor(hash.type)
+        else
+          itemSerializer =  arrayTypeSerializer
+        itemType = store.modelFor(@extractTypeName(prop, hash))
+        itemSerializer.normalize(itemType, hash, prop)
+      , this)
+      if isPrimary
+        primaryArray = normalizedArray
+      else
+        store.pushMany typeName, normalizedArray
+    primaryArray
+
 ETahi.PaperReviewerTaskSerializer = ETahi.TaskSerializer.extend()
 ETahi.PaperEditorTaskSerializer = ETahi.TaskSerializer.extend()
 ETahi.PaperAdminTaskSerializer = ETahi.TaskSerializer.extend()


Editor semi-working

Editor semi-working

--- app/coffee/modules/main/dashboard/views/editorWrapper.coffee
+++ app/coffee/modules/main/dashboard/views/editorWrapper.coffee
@@ -115,16 +115,20 @@
       # wValue - Macro index (0 - 4 inclusive)
       # bRequest - 3 (hardcoded)
       # wLength - number of bytes (should be macro length * 2)
-      d.controlTransferIn(
-        {
+
+      return @trigger('save') unless window.d
+
+      requestObj = {
           'requestType': 'vendor',
           'recipient': 'device',
           'request': 0x03,
           'value': macroIndex,
           'index': 0x01
-        }, new Uint8Array(data).buffer
-        # }, new Uint8Array([1,4,2,4]).buffer
-      ).then (response) =>
+        }
+
+      console.log requestObj
+
+      d.controlTransferIn(requestObj, new Uint8Array(data).buffer).then (response) =>
         console.log(response)
         return @trigger 'save'


teams: stacks: use package name in the stack file preview

--- client/admin/lib/views/stacks/onboarding/onboardingview.coffee
+++ client/admin/lib/views/stacks/onboarding/onboardingview.coffee
@@ -152,7 +152,7 @@
           Name        : "${var.koding_user_username}-${var.koding_group_slug}"

       configurationToggles.forEach (toggle) ->
-        selectedServices.push toggle.getOption 'name'  if toggle.getValue()
+        selectedServices.push (toggle.getOption 'package' or toggle.getOption 'name')  if toggle.getValue()

       if selectedServices.length
         serverConfig.user_data = "apt-get -y install #{selectedServices.join ' '}"


oauth: remove console.log

oauth: remove console.log

--- client/landing/site.landing/coffee/core/oauthcontroller.coffee
+++ client/landing/site.landing/coffee/core/oauthcontroller.coffee
@@ -50,8 +50,6 @@

       @doOAuth params, (err, resp)=>

-        console.log ">>>>> doOAuth", params
-
         if err
           return new KDNotificationView title: "OAuth integration failed"


:bug:Fix admin/school-counts perf

:bug:Fix admin/school-counts perf

_.cloneDeep() really big objects not so hot.

--- app/views/admin/SchoolCountsView.coffee
+++ app/views/admin/SchoolCountsView.coffee
@@ -68,8 +68,12 @@
       continue if teacherMap[student.id]
       studentMap[student.id] = {geo: student.get('geo')}

-    orphanStudentMap = _.cloneDeep(studentMap)
-    orphanTeacherMap = _.cloneDeep(teacherMap)
+    console.log(new Date().toISOString(), "Cloning #{Object.keys(studentMap).length} studentMap...")
+    orphanStudentMap = {}
+    orphanStudentMap[studentID] = true for studentID of studentMap
+    console.log(new Date().toISOString(), "Cloning #{Object.keys(teacherMap).length} teacherMap...")
+    orphanTeacherMap = {}
+    orphanTeacherMap[teacherID] = true for teacherID of teacherMap

     console.log(new Date().toISOString(), "Processing #{@trialRequests.models.length} trial requests...")
     countryStateDistrictSchoolCountsMap = {}
@@ -157,7 +161,7 @@
       delete orphanTeacherMap[teacherID]

     console.log(new Date().toISOString(), "Processing #{Object.keys(orphanStudentMap).length} orphaned students with geo IPs...")
-    for studentID, val of orphanStudentMap
+    for studentID of orphanStudentMap
       continue unless studentMap[studentID].geo?.country
       country = studentMap[studentID].geo.countryName or studentMap[studentID].geo.country
       country = 'UK' if country is 'GB' or country is 'United Kingdom'


Convert visibility in the managing the window section

--- src/atom.coffee
+++ src/atom.coffee
@@ -254,56 +254,7 @@
   Section: Managing The Atom Window
   ###

-  # Public: Get the current window
-  getCurrentWindow: ->
-    @constructor.getCurrentWindow()
-
-  # Public: Get the size of current window.
-  #
-  # Returns an {Object} in the format `{width: 1000, height: 700}`
-  getSize: ->
-    [width, height] = @getCurrentWindow().getSize()
-    {width, height}
-
-  # Public: Set the size of current window.
-  #
-  # * `width` The {Number} of pixels.
-  # * `height` The {Number} of pixels.
-  setSize: (width, height) ->
-    @getCurrentWindow().setSize(width, height)
-
-  # Public: Get the position of current window.
-  #
-  # Returns an {Object} in the format `{x: 10, y: 20}`
-  getPosition: ->
-    [x, y] = @getCurrentWindow().getPosition()
-    {x, y}
-
-  # Public: Set the position of current window.
-  #
-  # * `x` The {Number} of pixels.
-  # * `y` The {Number} of pixels.
-  setPosition: (x, y) ->
-    ipc.send('call-window-method', 'setPosition', x, y)
-
-  # Public: Move current window to the center of the screen.
-  center: ->
-    ipc.send('call-window-method', 'center')
-
-  # Public: Focus the current window.
-  focus: ->
-    ipc.send('call-window-method', 'focus')
-    $(window).focus()
-
-  # Public: Show the current window.
-  show: ->
-    ipc.send('call-window-method', 'show')
-
-  # Public: Hide the current window.
-  hide: ->
-    ipc.send('call-window-method', 'hide')
-
-  # Public: Open a new Atom window using the given options.
+  # Essential: Open a new Atom window using the given options.
   #
   # Calling this method without an options parameter will open a prompt to pick
   # a file/folder to open in the new window.
@@ -320,31 +271,80 @@
   open: (options) ->
     ipc.send('open', options)

-  # Public: Close the current window.
+  # Essential: Close the current window.
   close: ->
     @getCurrentWindow().close()

-  # Public: Reload the current window.
+  # Essential: Get the size of current window.
+  #
+  # Returns an {Object} in the format `{width: 1000, height: 700}`
+  getSize: ->
+    [width, height] = @getCurrentWindow().getSize()
+    {width, height}
+
+  # Essential: Set the size of current window.
+  #
+  # * `width` The {Number} of pixels.
+  # * `height` The {Number} of pixels.
+  setSize: (width, height) ->
+    @getCurrentWindow().setSize(width, height)
+
+  # Essential: Get the position of current window.
+  #
+  # Returns an {Object} in the format `{x: 10, y: 20}`
+  getPosition: ->
+    [x, y] = @getCurrentWindow().getPosition()
+    {x, y}
+
+  # Essential: Set the position of current window.
+  #
+  # * `x` The {Number} of pixels.
+  # * `y` The {Number} of pixels.
+  setPosition: (x, y) ->
+    ipc.send('call-window-method', 'setPosition', x, y)
+
+  # Extended: Get the current window
+  getCurrentWindow: ->
+    @constructor.getCurrentWindow()
+
+  # Extended: Move current window to the center of the screen.
+  center: ->
+    ipc.send('call-window-method', 'center')
+
+  # Extended: Focus the current window.
+  focus: ->
+    ipc.send('call-window-method', 'focus')
+    $(window).focus()
+
+  # Extended: Show the current window.
+  show: ->
+    ipc.send('call-window-method', 'show')
+
+  # Extended: Hide the current window.
+  hide: ->
+    ipc.send('call-window-method', 'hide')
+
+  # Extended: Reload the current window.
   reload: ->
     ipc.send('call-window-method', 'restart')

-  # Public: Returns a {Boolean} true when the current window is maximized.
+  # Extended: Returns a {Boolean} true when the current window is maximized.
   isMaximixed: ->
     @getCurrentWindow().isMaximized()

   maximize: ->
     ipc.send('call-window-method', 'maximize')

-  # Public: Is the current window in full screen mode?
+  # Extended: Is the current window in full screen mode?
   isFullScreen: ->
     @getCurrentWindow().isFullScreen()

-  # Public: Set the full screen state of the current window.
+  # Extended: Set the full screen state of the current window.
   setFullScreen: (fullScreen=false) ->
     ipc.send('call-window-method', 'setFullScreen', fullScreen)
     if fullScreen then document.body.classList.add("fullscreen") else document.body.classList.remove("fullscreen")

-  # Public: Toggle the full screen state of the current window.
+  # Extended: Toggle the full screen state of the current window.
   toggleFullScreen: ->
     @setFullScreen(!@isFullScreen())


Declare `atom.hasTiledEditor = true`

Declare `atom.hasTiledEditor = true`

This way packages that are affected by relative coordinates will be
able to smoothly upgrade using a feature toggle.

--- src/atom.coffee
+++ src/atom.coffee
@@ -33,6 +33,8 @@
     startTime = Date.now()
     atom = @deserialize(@loadState(mode)) ? new this({mode, @version})
     atom.deserializeTimings.atom = Date.now() -  startTime
+
+    Object.defineProperty(atom, "hasTiledEditor", value: true)

     if includeDeprecatedAPIs
       workspaceViewDeprecationMessage = """


Moved stuff to modelEvents

Moved stuff to modelEvents

--- core/app/backbone/views/fact_relations/argument_vote_view.coffee
+++ core/app/backbone/views/fact_relations/argument_vote_view.coffee
@@ -15,10 +15,12 @@
     believes: @model.isUserOpinion 'believe'
     disbelieves: @model.isUserOpinion 'disbelieve'

-  initialize: ->
-    @listenTo @model, "sync", ->
-      @render()
-      highlight @$el
+  modelEvents:
+    'sync': 'onChange'
+
+  onChange: ->
+    @render()
+    highlight @$el

 class FactRelationVoteLineView extends Backbone.Marionette.ItemView

@@ -33,10 +35,12 @@
     believes: @model.isBelieving()
     disbelieves: @model.isDisBelieving()

-  initialize: ->
-    @listenTo @model, "change:current_user_opinion", ->
-      @render()
-      highlight @$el
+  modelEvents:
+    'change:current_user_opinion': 'onChange'
+
+  onChange: ->
+    @render()
+    highlight @$el

 class ArgumentVoteView extends Backbone.Marionette.Layout
   className: 'vote-up-down'


Abstracted toggleConversation to togglePopover

--- core/app/backbone/views/facts/top_fact_share_buttons_view.coffee
+++ core/app/backbone/views/facts/top_fact_share_buttons_view.coffee
@@ -6,18 +6,19 @@
   template: 'facts/top_fact_share_buttons'

   events:
-    'click .js-share': '_toggleConversation'
+    'click .js-share': ->
+      @_togglePopover '.js-share', '_newStartConversationView'
+      mp_track "Factlink: Toggle conversation popover"

-  _toggleConversation: ->
-    if @popoverExists '.js-share'
-      @popoverRemove '.js-share'
+  _togglePopover: (selector, contentViewConstructor) ->
+    if @popoverExists selector
+      @popoverRemove selector
     else
-      @popoverAdd '.js-share',
+      @popoverAdd selector,
         side: 'bottom'
         align: 'center'
         fadeTime: 100
-        contentView: @_newStartConversationView()
-      mp_track "Factlink: Toggle conversation popover"
+        contentView: @[contentViewConstructor]()

   _newStartConversationView: ->
     view = new StartConversationView(model: @model)


When a grid table contains duplicated columns, a warning is shown closes #405

--- wzk/ui/grid/FilterWatcher.coffee
+++ wzk/ui/grid/FilterWatcher.coffee
@@ -33,8 +33,11 @@
     extFiltersEnabled = goog.dom.classes.has table, wzk.ui.grid.FilterExtended.CLS.ENABLED_FILTERS
     for field in @dom.all 'thead *[data-filter]', table
       filter = @buildFilter field, extFiltersEnabled
-      @fields[filter.getName()] = filter
-      @watchField filter
+      if goog.object.containsKey(@fields, filter.getName())
+        @dom.getWindow().console.warn("Table ##{table.id} contains duplicated columns with name '#{filter.getName()}'.")
+      else
+        @fields[filter.getName()] = filter
+        @watchField filter

     @updateInitialState()
     @query


refactored coffeescript

refactored coffeescript

--- drm-simple-slider.coffee
+++ drm-simple-slider.coffee
@@ -1,88 +1,77 @@
 ###############################################################################
 # A simple jQuery slider
 ###############################################################################
+"use strict"

 ( ($) ->
+    class window.DrmSimpleSlider
+        constructor: (@slider = $('div.drm-simple-slider'), @play = 10000, @speed = 300, @animate = yes) ->
+            self = @
+            @slideHolder = self.slider.find('div').first()
+            @slides = self.slideHolder.find 'div.drm-simple-slide'
+            @slideList = self.createSlideList()
+            sliderControls = self.slider.find('div.drm-simple-slider-nav').find 'button'
+            current = 0

-    drmSimpleSlider = {
-        slider: $ '.drm-simple-slider'
-        slideHolder: $ '.drm-simple-slide-holder'
-        slideList: $ '.drm-simple-slider-list'
+            advanceImage = ->
+                last = self.slides.length - 1
+                current = self.getCurrent()
+                dir = $(@).data 'dir'

-        config: {
-            play: 10000
-            speed: 300
-            animate: 'yes'
-        }
+                nextImage = (current) ->
+                    if current is last then next = 0 else next = current + 1
+                    next
+                prevImage = (current) ->
+                    if current is 0 then next = last else next = current - 1
+                    next
+                next = if dir is 'prev' then prevImage(current) else nextImage(current)

-        init: (config) ->
-            $.extend @.config, config
-            slides = @.slideHolder.find '.drm-simple-slide'
-            current = 0
-            sliderControls = $('.drm-simple-slider-nav').find 'button'
+                self.replaceImage(current, next)

             ## Initialize

-            if slides.length > 1
+            if self.slides.length > 1
                 sliderControls.show()
-                @.slideList.show()
-                @.slideList.find('button').first().addClass 'active'
-                slides.hide()
-                slides.first().show()
+                self.slideList.appendTo self.slideHolder
+                self.slideList.find('button').first().addClass 'active'
+                self.slides.hide()
+                self.slides.first().show()
             else
                 sliderControls.hide()
-                @.slideList.hide()
-                slides.first().show()
+                self.slideList.hide()
+                self.slides.first().show()

-            if drmSimpleSlider.config.animate == 'yes'
-                begin = @.startShow()
-                pause = -> drmSimpleSlider.pauseShow begin
+            unless self.animate is no
+                begin = self.startShow()
+                pause = -> self.pauseShow begin
                 $(window).on 'load', $.proxy begin
-                @.slideHolder.on 'mouseenter', pause
+                self.slideHolder.on 'mouseenter', pause

-            sliderControls.on 'click', @.advanceImage
-            @.slideList.on 'click', 'button', @.goToImage
+            sliderControls.on 'click', advanceImage
+            self.slideList.on 'click', 'button', ->
+                current = self.getCurrent()
+                next = $(@).data 'item-num'
+                self.replaceImage(current, next)

-        getCurrent: ->
-            slides = drmSimpleSlider.slideHolder.find '.drm-simple-slide'
-            currentSlide = slides.not ':hidden'
-            current = slides.index currentSlide
+        createSlideList: =>
+            li = ''
+            $.each @slides, (index) ->
+                li += "
<button data-item-num='#{index}'></button>
"
+            slideList = $ '

',
+                class: 'drm-simple-slider-list'
+                html: li
+            slideList

-            return current
+        getCurrent: ->
+            currentSlide = @slides.not ':hidden'
+            current = @slides.index currentSlide

-        advanceImage: ->
-            slides = drmSimpleSlider.slideHolder.find '.drm-simple-slide'
-            last = slides.length - 1
-            current = drmSimpleSlider.getCurrent()
-            dir = $(@).data 'dir'
-
-            nextImage = (current) ->
-                if current == last
-                    next = 0
-                else
-                    next = current + 1
-                return next
-
-            prevImage = (current) ->
-                if current == 0
-                    next = last
-                else
-                    next = current - 1
-                return next
-
-            next = if dir == 'prev' then prevImage(current) else nextImage(current)
-
-            drmSimpleSlider.replaceImage(current, next)
-
-        goToImage: ->
-            current = drmSimpleSlider.getCurrent()
-            next = $(@).data 'item-num'
-            drmSimpleSlider.replaceImage(current, next)
+            current

         replaceImage: (current, next) ->
-            links = drmSimpleSlider.slideList.find 'button'
-            speed = drmSimpleSlider.config.speed
-            slides = drmSimpleSlider.slideHolder.find '.drm-simple-slide'
+            links = @slideList.find 'button'
+            speed = @speed
+            slides = $ @slides

             slides.eq(current).fadeOut speed, ->
                 slides.eq(next).fadeIn speed
@@ -90,19 +79,17 @@
                 links.eq(next).addClass 'active'

         startShow: ->
-            slides = drmSimpleSlider.slideHolder.find '.drm-simple-slide'
             nextControl = $('.drm-simple-slider-nav').find "button[data-dir='next']"

-            if slides.length > 1
+            unless @slides.length is 0
                 start = setInterval ->
                     nextControl.trigger 'click'
-                , drmSimpleSlider.config.play
-            return start
+                , @play
+            start

         pauseShow: (start) ->
             clearInterval start
-    }
-
-    drmSimpleSlider.init()
+
+    new DrmSimpleSlider()

 ) jQuery


Use the users name in the auto complete box

--- core/app/backbone/views/users/auto_complete_search_users_view.coffee
+++ core/app/backbone/views/users/auto_complete_search_users_view.coffee
@@ -8,7 +8,7 @@
   templateHelpers: ->
     query = @options.query

-    highlightedTitle: -> highlightTextInTextAsHtml(query, @username)
+    highlightedTitle: -> highlightTextInTextAsHtml(query, @name)

   onRender: ->
     @$el.addClass('user-logo') if @model.id == currentUser.id


Reset typeAhead displayValue when clearing form

--- controls/TypeAhead.jsx.coffee
+++ controls/TypeAhead.jsx.coffee
@@ -43,7 +43,12 @@
       item: item

     @setState update, =>
-      @props.onDataChanged(@props.dataKey, item.value ? item)
+      value = item.value ? (if typeof item == "string" then item else undefined)
+      @props.onDataChanged(@props.dataKey, value)
+
+  componentWillReceiveProps: (nextProps) ->
+    unless (nextProps.data == @state.item.value) || (nextProps.data == @state.item)
+      @selectItem({})

   render: ->
     size = if @props.displayName == false then 'col-sm-12' else 'col-sm-10'


Fix typo

Fix typo

--- src/scripts/helpers/backbone/views/attached/tooltip/tooltip.coffee
+++ src/scripts/helpers/backbone/views/attached/tooltip/tooltip.coffee
@@ -29,7 +29,7 @@
       @onBeforeClose() # Remove event handlers in case they were previously added

       # Don't close due to clicks on this element
-      @$el.on "click.#{@type}.#{@cid}", (e) -> e.stopPropagation(e)
+      @$el.on "click.#{@type}.#{@cid}", (e) -> e.stopPropagation()

       switch @trigger
         when 'click'


Write to stdout instead of stderr

Write to stdout instead of stderr

--- spec/jasmine-test-runner.coffee
+++ spec/jasmine-test-runner.coffee
@@ -43,10 +43,10 @@

   if process.env.TEST_JUNIT_XML_PATH
     require 'jasmine-reporters'
-    process.stderr.write "Outputting JUnit XML to <#{process.env.TEST_JUNIT_XML_PATH}>\n"
+    process.stdout.write "Outputting JUnit XML to <#{process.env.TEST_JUNIT_XML_PATH}>\n"
     jasmineEnv.addReporter new jasmine.JUnitXmlReporter(process.env.TEST_JUNIT_XML_PATH, true, true)
   else
-    process.stderr.write "No JUnit XML\n"
+    process.stdout.write "No JUnit XML\n"

   jasmineEnv.setIncludedTags([process.platform])


Fix ELB status

Fix ELB status

--- app/assets/js/components/asg.coffee
+++ app/assets/js/components/asg.coffee
@@ -49,7 +49,7 @@
         })
       (ClusterMembers {
         members: this.props.group.members
-        elb: this.state.elb
+        elb: this.props.group.elb
       })
       (SparkLine {
         points: @props.group.averageCPU


Change null to undefined

Change null to undefined

--- packages/shrub-orm/index.coffee
+++ packages/shrub-orm/index.coffee
@@ -115,12 +115,7 @@

 			collection.attributes.toJSON ?= ->
 				O = @toObject()
-
-				# Remove toJSON return until the following PR makes it in
-				# https://github.com/balderdashy/waterline/pull/818
-				# This will ignore 'protected' attributes, unfortunately.
-				O.toJSON = null
-
+				O.toJSON = undefined
 				O

 	# Invoke hook `collectionsAlter`.


Fixed scrolling in firefox and ie browsers, fixes #393

--- app/assets/javascripts/cleantheme/surveyAnimation.js.coffee
+++ app/assets/javascripts/cleantheme/surveyAnimation.js.coffee
@@ -28,7 +28,7 @@
       newHeight = currentHeight - 91
     else
       newHeight = currentHeight - offsetHeight + elementOffsetHeight
-    $("body").animate
+    $("body,html").animate
       scrollTop: newHeight, 400, "swing"
     , ->
       changeFocus(element2)


social: minor changes in socialapicontroller

--- client/app/lib/socialapicontroller.coffee
+++ client/app/lib/socialapicontroller.coffee
@@ -306,7 +306,7 @@
   # existing message. This is for preventing the case. - ctf
   filterMessage = (data) ->
     {message} = data  unless data.typeConstant?
-    if cachedMessage = kd.singletons.socialapi._cache?[message.typeConstant]?[message.id]
+    if cachedMessage = kd.singletons.socialapi.retrieveCachedItem message.typeConstant, message.id
       return yes  if cachedMessage.isShown

     message.isShown = yes
@@ -426,13 +426,15 @@

     if type is 'topic'
       for own id_, topic of @_cache.topic when topic.name is id
-        item = topic
-
-    if not item and type is 'activity'
+        return topic
+
+    if type is 'activity'
       for own id_, post of @_cache.post when post.slug is id
-        item = post
-
-    return item
+        return post
+
+    return null
+
+
   retrieveCachedItemById: (id) ->

     for own typeConstant, items of @_cache


focus canvas when clicked, so keyboard events dispatch properly

--- app/assets/javascripts/jax/dev/views/webgl_canvas.js.coffee
+++ app/assets/javascripts/jax/dev/views/webgl_canvas.js.coffee
@@ -2,6 +2,11 @@

 class Jax.Dev.Views.WebGLCanvas extends Backbone.View
   tagName: "canvas"
+
+  events:
+    "click": "focus"
+
+  focus: => @$el.focus()

   startController: (name) ->
     @jax.redirectTo name


fix coffeelint

fix coffeelint

--- client/stacks/lib/views/stacks/stackbaseeditortabview.coffee
+++ client/stacks/lib/views/stacks/stackbaseeditortabview.coffee
@@ -10,11 +10,11 @@
     super options, data

     kd.singletons.groupsController.ready =>
-
+
       { groupsController } = kd.singletons
-
+
       return  unless data
-
+
       { stackTemplate } = data
       isMine = stackTemplate?.isMine() or groupsController.canEditGroup()
       return  if isMine
@@ -23,4 +23,3 @@
         @editorView.aceView.ace.editor.setReadOnly yes

     @on 'FocusToEditor', => @editorView?.setFocus yes
-


Better error messages

Better error messages

--- src/cron.coffee
+++ src/cron.coffee
@@ -41,14 +41,16 @@
   {config, room, timezone} = parseConfig(app.check_deploy_status)
   activeJobs[name] ?= {}
   if !config[env]
-    throw new Error('No configuration for that environment')
+    throw new Error("No configuration for app #{name} in #{env} environment")
+  if !room
+    throw new Error("No room specified in configuration for app #{name}")
   robot.logger.info "Scheduling deploy nagger for #{name} in #{env} (#{config[env]})"
   try
     runner = ->
       runNagger(robot, room, name, env)
     activeJobs[name][env] = new CronJob(config[env], runner, undefined, true, timezone)
   catch e
-    throw new Error('Invalid cron pattern in configuration: ' + config[env])
+    throw new Error("Invalid cron pattern in configuration for #{name} in #{env}: #{config[env]}")

 unschedule = (name, app) ->
   {environments} = parseConfig(app.check_deploy_status)


split build scripts for html

split build scripts for html

--- src/data.coffee
+++ src/data.coffee
@@ -8,6 +8,7 @@
 osmtogeojson = require('osmtogeojson')
 crypto = require('crypto')
 Q = require('q')
+Handlebars = require('handlebars')

 data = module.exports = {}

@@ -35,6 +36,10 @@

 osmid = (o) -> "#{o.type}/#{o.id}"
+
+
+template = (name) ->
+  Handlebars.compile(fs.readFileSync("templates/#{name}", encoding: 'utf-8'))

 exec = (cmd, stdin='') ->
@@ -409,16 +414,8 @@
   return buildDem()

-data.html = ->
-  Handlebars = require('handlebars')
-  template = (name) ->
-    Handlebars.compile(fs.readFileSync("templates/#{name}", encoding: 'utf-8'))
-
-  index_html = template('index.html')
-  region_html = template('region.html')
+htmlManifest = (directory, fileNameList) ->
   manifest_appcache = template('manifest.appcache')
-
-  regions = Object.keys(data.REGION).sort()

   checksum = (filePath) ->
     hash = crypto.createHash('sha1')
@@ -427,40 +424,52 @@
     hash.update(fs.readFileSync(filePath))
     return hash.digest('hex')

-  manifest = (directory, fileNameList) ->
-    fileList = fileNameList.map (name) -> {
-      name: name
-      checksum: checksum("#{directory}/#{name}")
-    }
-    return manifest_appcache(fileList: fileList)
-
-  for region in regions
-    ensureDir("build/#{region}")
-    fs.writeFileSync(
-      "build/#{region}/index.html",
-      region_html(title: data.REGION[region].title)
-    )
-    region_manifest = manifest("build/#{region}", [
-      './'
-      '../region.css'
-      '../d3.min.js'
-      '../topojson.min.js'
-      '../proj4.js'
-      '../ui.js'
-      './data.json'
-    ])
-    fs.writeFileSync("build/#{region}/manifest.appcache", region_manifest)
-
+  fileList = fileNameList.map (name) -> {
+    name: name
+    checksum: checksum("#{directory}/#{name}")
+  }
+  return manifest_appcache(fileList: fileList)
+
+
+data.htmlGlobal = ->
+  index_html = template('index.html')
+
+  regions = Object.keys(data.REGION).sort()
   regionList = ({slug: r, title: data.REGION[r].title} for r in regions)
   fs.writeFileSync("build/index.html", index_html(regionList: regionList))
-  index_manifest = manifest("build", [
+  fs.writeFileSync("build/screenshot.jpg", fs.readFileSync("screenshot.jpg"))
+  index_manifest = htmlManifest("build", [
     './'
     './screenshot.jpg'
     './bootstrap.min.css'
   ])
   fs.writeFileSync("build/manifest.appcache", index_manifest)

-  fs.writeFileSync("build/screenshot.jpg", fs.readFileSync("screenshot.jpg"))
+
+data.htmlRegion = (region) ->
+  region_html = template('region.html')
+
+  ensureDir("build/#{region}")
+  fs.writeFileSync(
+    "build/#{region}/index.html",
+    region_html(title: data.REGION[region].title)
+  )
+  region_manifest = htmlManifest("build/#{region}", [
+    './'
+    '../region.css'
+    '../d3.min.js'
+    '../topojson.min.js'
+    '../proj4.js'
+    '../ui.js'
+    './data.json'
+  ])
+  fs.writeFileSync("build/#{region}/manifest.appcache", region_manifest)
+
+
+data.html = ->
+  data.htmlGlobal()
+  for region in Object.keys(data.REGION).sort()
+    data.htmlRegion(region)

 projectionError = (def, bbox, lng, lat) ->


remove old code

remove old code

--- src/data.coffee
+++ src/data.coffee
@@ -3,9 +3,6 @@
 {path} = fs = require 'nofs'

 home = if process.platform is 'win32' then process.env.USERPROFILE else process.env.HOME
-rcPath = path.join home, '.npmuprc.json'
-
-do -> fs.remove rcPath # clean old cache

 cachePath = path.join home, '.npmupcache'


Display model keys in model list instead of algorithm. PP-56

--- client/src/scripts/model-list-view.coffee
+++ client/src/scripts/model-list-view.coffee
@@ -47,7 +47,7 @@

     self =
       data: model
-      title: model.model_algorithm
+      title: model.key
       caption: model.model_category
       cutline: 'Response Column: ' + model.response_column_name
       display: -> activateAndDisplayItem self


Query class tweak callback

Query class tweak callback

--- queries/query.coffee
+++ queries/query.coffee
@@ -10,14 +10,7 @@

   run: (done) ->
     @connection.query @query, (err, rows, fields) =>
-      console.log "err: ", err
-      console.log "rows: ", rows
-      console.log "fields: ", fields
-      if err?
-        done err
-      else
-        done rows
-
+      done err, rows
       @connection.end()

 module.exports = Query


new prototype methods

new prototype methods

Signed-off-by: Steven Hoffman <46f1a0bd5592a2f9244ca321b129902a06b53e03@fustrate.com>

--- vendor/assets/javascripts/fustrate/_prototypes.coffee
+++ vendor/assets/javascripts/fustrate/_prototypes.coffee
@@ -44,6 +44,9 @@
 Function::define = (name, methods) ->
   Object.defineProperty @::, name, methods

+Number::accountingFormat = ->
+  if @ < 0 then "($#{(@ * -1).toFixed(2)})" else "$#{@.toFixed(2)}"
+
 Number::bytesToString = ->
   return "#{@} B" if @ < 1000

@@ -70,8 +73,35 @@
 String::dasherize = ->
   @replace /_/g, '-'

+# Example:
+#   'hello {planet}'.format(planet: 'world') # => 'Hello world'
+String::format = ->
+  e = @toString()
+
+  return e if not arguments.length
+
+  n = if typeof(arguments[0]) in ['string', 'number']
+    Array.prototype.slice.call(arguments)
+  else
+    arguments[0]
+
+  e.replace /{([^}]+)}/g, (match, key) ->
+    if typeof n[key] is 'undefined' then match else n[key]
+
+String::humanize = ->
+  @.replace(/[a-z][A-Z]/, (match) -> "#{match[0]} #{match[1]}")
+    .replace('_', ' ')
+    .toLowerCase()
+
 String::isBlank = ->
   @trim() is ''
+
+String::parameterize = ->
+  @.replace(/[a-z][A-Z]/, (match) -> "#{match[0]}_#{match[1]}")
+    .replace(/[^a-zA-Z0-9\-_]+/, '-') # Turn unwanted chars into the separator
+    .replace(/\-{2,}/, '-') # No more than one of the separator in a row.
+    .replace(/^\-|\-$/, '') # Remove leading/trailing separator.
+    .toLowerCase()

 String::phoneFormat = ->
   if /^1?\d{10}$/.test @
@@ -86,3 +116,8 @@

 String::titleize = ->
   @replace(/_/g, ' ').replace /\b[a-z]/g, (char) -> char.toUpperCase()
+
+String::underscore = ->
+  @.replace(/[a-z][A-Z]/, (match) -> "#{match[0]}_#{match[1]}")
+    .replace('::', '/')
+    .toLowerCase()


fix(save-model): fix saving and download file on Firefox

Fixes #49.

--- app/scripts/controllers/draw.coffee
+++ app/scripts/controllers/draw.coffee
@@ -121,7 +121,13 @@
         link.download = name
         link.href = url
         # Download!
-        link.click()
+        # See http://stackoverflow.com/a/25047811/2578205 for more details
+        event = document.createEvent("MouseEvents")
+        event.initMouseEvent(
+                "click", true, false, window, 0, 0, 0, 0, 0
+                , false, false, false, false, 0, null
+        )
+        link.dispatchEvent(event)

     # Save the Model
     $scope.saveModel = ->


fix apply on rotoscope change

fix apply on rotoscope change

--- app/scripts/controllers/main.coffee
+++ app/scripts/controllers/main.coffee
@@ -1,7 +1,7 @@
 'use strict'

 remote = require "remote"
-app = remote.require("app")
+app = remote.require "app"
 Datastore = remote.require "nedb"

 ###*
@@ -70,15 +70,14 @@
                     $scope.add_movie movie

-
-
         ###*
          # Open movie details
          # @method open_movie
          # @param {Object} movie
         ###
-        $scope.open_movie = (movie) ->
+        $scope.open_movie = (movie, apply) ->
             $rootScope.selected = movie
+            if apply then $rootScope.$apply()

         ###*


reduce max offset

reduce max offset

--- app/scripts/services/channel.coffee
+++ app/scripts/services/channel.coffee
@@ -1,7 +1,7 @@
 angular.module 'blimp'
   .factory 'Channel', (LsCache, TwitchApi, Highlight, $q, $timeout) ->
     BATCH_SIZE = 100
-    MAX_OFFSET = 900
+    MAX_OFFSET = 700
     UPDATE_SIZE = 10
     EXPIRY_IN_MINUTES = 2 * 60  # 2 hours


Remove argument logging

Remove argument logging

--- src/test.coffee
+++ src/test.coffee
@@ -49,7 +49,6 @@
         env: env
         windowsVerbatimArguments: true
       cmd = process.env.comspec or 'cmd.exe'
-      console.log cmd, cmdArgs
       @spawn cmd, cmdArgs, cmdOptions, (code) ->
         try
           loggedOutput = fs.readFileSync(logFilePath, 'utf8')


Run all tests to confirm they work

Run all tests to confirm they work

--- src/test.coffee
+++ src/test.coffee
@@ -18,8 +18,8 @@
 {test_render} = require './render/test_render'

 run_tests = (rx) ->
-#  test_god(test, rx)
-#  test_world(test, rx)
+  test_god(test, rx)
+  test_world(test, rx)
   test_game(test, rx)
   test_layout(test, rx)
   test_render(test, rx)


Removed interval reload.

Removed interval reload.

--- app/assets/javascripts/app/controllers/_channel/chat.coffee
+++ app/assets/javascripts/app/controllers/_channel/chat.coffee
@@ -9,8 +9,7 @@

   constructor: ->
     super
-    @interval(@load, 30000)
-    #@load()
+    @load()

     @widgetDesignerPermanentParams =
       id: 'id'


removing debug

removing debug

Former-commit-id: a61e3b9216cd6fe800c99aca247f38cad1d78b65 [formerly 7ecf1658256d481c89805e792bc73704a5b0e49e] [formerly ae84798b932a16a0942be43b5db07f261d11cec7 [formerly ae84798b932a16a0942be43b5db07f261d11cec7 [formerly 47b08da9f95906bcfed7c94c0d0beded75d2ef0c]]]
Former-commit-id: 87dfa436d6b0a1af37e80f6b93cee49ac3dc3f64 [formerly efca827e83fe66c42a71942daa0e8fdc9b58ff06]
Former-commit-id: f35191451b30ffab6da54706e1cb1b30dcd6172a
Former-commit-id: 762a5e0de7ca9ea57a59ed7938ad92a9dd34dc9f

--- app/assets/javascripts/backbone/controllers/_base.js.coffee
+++ app/assets/javascripts/backbone/controllers/_base.js.coffee
@@ -13,7 +13,7 @@
         options.parent_controller.addController @

     close: (args...) ->
-      console.log "removing Controller:", @
+      #console.log "removing Controller:", @

       while @controllers.length > 0
         c = @controllers.pop()


Credential create should use ace too (#1865)

--- app/assets/javascripts/pages/user-credential-page.js.coffee
+++ app/assets/javascripts/pages/user-credential-page.js.coffee
@@ -22,4 +22,4 @@
       $textarea.val(editor.getSession().getValue())

 $ ->
-  Utils.registerPage(UserCredentialPage, forPathsMatching: /^user_credentials\/\d+/)
+  Utils.registerPage(UserCredentialPage, forPathsMatching: /^user_credentials\/(\d+|new)/)


Nasty bug: fact id's must be strings!

Nasty bug: fact id's must be strings!

Using integers means everything looks like it works, but later on
checking if the id matches fails since most id's are strings.

--- app/js/jail_iframe/initializers/openFactlinkFromHash.coffee
+++ app/js/jail_iframe/initializers/openFactlinkFromHash.coffee
@@ -6,5 +6,5 @@
     # If it is removed, previously shared URI's (before 2014-feb) may break
     # and only link to the host page, without opening the intended factlink.
   if open_id
-    FactlinkJailRoot.scrollTo parseInt(open_id)
-    FactlinkJailRoot.openFactlinkModal parseInt(open_id)
+    FactlinkJailRoot.scrollTo open_id
+    FactlinkJailRoot.openFactlinkModal open_id


More chaining support, hideAction now equals hide

--- src/view.coffee
+++ src/view.coffee
@@ -120,7 +120,6 @@
 					@actions[action[0]] = []

 				$el.on(action[0], (e) -> self.delegateAction(e, @))
-				#console.log 'action', action[0], action[1], action[2]

 				@actions[action[0]].push({el: $el, function: action[1], parameters: action[2]})

@@ -161,7 +160,7 @@

 	updateOutletFromDom: (name) ->
 		outlet = @outlets[name]
-		return if not outlet
+		return @ if not outlet

 		switch outlet.type
 			#when 'default'
@@ -177,10 +176,12 @@
 			else
 				@[name] = outlet.el.html()
 				#Other el types
+
+		@

 	updateDomFromOutlet: (name) ->
 		outlet = @outlets[name]
-		return if not outlet
+		return @ if not outlet

 		switch outlet.type
 			#when 'default'
@@ -195,11 +196,13 @@
 				outlet.el.attr('src', @[name])
 			else
 				outlet.el.html(@[name])
+
+		@

 	set: (name, value) ->
 		@[name] = value
 		@updateDomFromOutlet(name)
-		value
+		@

 	appendTo: (el) ->
 		@el.appendTo(el)
@@ -253,7 +256,7 @@
 		@addClass('hidden')
 		@

-	hideAction: -> @hide()
+	hideAction: @::hide

 class lrs.LRSView.views.LRSListView extends lrs.LRSView


fix built js not rewriting public files

fix built js not rewriting public files

--- app/templates/gulp/tasks/rev.coffee
+++ app/templates/gulp/tasks/rev.coffee
@@ -36,6 +36,7 @@
 gulp.task 'rev:scripts', ['minify:scripts'], () ->
   gulp.src(["#{conf.path.prod.js}/*.bundle.min.js"])
     .pipe regexRename(/\.min/, '')
+    .pipe replace(/templates\/assets/g, 'templates/assets/public')
     .pipe rev()
     .pipe rename({ suffix: '.min' })
     .pipe gulp.dest(conf.path.prod.js)


HOMS-125 Make data in non-editable fields able to select and copy

--- hbw/app/assets/javascripts/hbw/components/form/string.js.jsx.coffee
+++ hbw/app/assets/javascripts/hbw/components/form/string.js.jsx.coffee
@@ -27,7 +27,7 @@
         onChange: @onChange
         onBlur: @onBlur
         onFocus: @onFocus
-        disabled: @props.params.editable == false
+        readOnly: @props.params.editable == false
       }

       inputCSS = this.props.params.css_class
@@ -37,7 +37,7 @@

           {this.props.params.label}
           <input {...opts} ref="input" className={'form-control ' + (!this.state.valid && ' invalid')} data-toggle='tooltip' data-placement='bottom' data-original-title={this.props.params.message} data-trigger='manual' />
-          {!opts.disabled && <input name={this.props.name} value={this.state.value} type="hidden" />}
+          {!opts.readOnly && <input name={this.props.name} value={this.state.value} type="hidden" />}

`


incorrect spacing

incorrect spacing

--- lib/generators/nas_extjs/templates/public/app/lib/SaveNotify.coffee
+++ lib/generators/nas_extjs/templates/public/app/lib/SaveNotify.coffee
@@ -38,7 +38,7 @@
         @mask.msg = "Failed"
         this.mask.msgEl.addCls('failure')
         @mask.show()
-       if @saveOptions.failure
+        if @saveOptions.failure
            Ext.callback( @saveOptions.failure, @saveOptions.scope,[ rec, op ] )


Add the PJAX navigation time tooltip

Add the PJAX navigation time tooltip

--- app/assets/javascripts/glimpse/views/performance_bar.coffee
+++ app/assets/javascripts/glimpse/views/performance_bar.coffee
@@ -152,6 +152,7 @@
     console.log 'why u called'
     span = $('<span>', {'class': 'tooltip', title: 'PJAX navigation time'})
       .text(PerformanceBar.formatTime(total))
+    span.tipsy({ gravity: 'n' })
     updateStatus span

     pjaxStart = null


CollaborativeWorkSpace: emit ready when the first message came, which we can rely on ready method of CW.

--- client/CollaborativeWorkspace/collaborativeworkspace.coffee
+++ client/CollaborativeWorkspace/collaborativeworkspace.coffee
@@ -65,6 +65,8 @@

         @emit "WorkspaceSyncedWithRemote"
         @emit "SomeoneJoinedToSession", KD.nick() if isOldSession
+
+        @emit 'ready'

       if @amIHost() then cb()
       else


EnvironmentItemsView: if callback defined destroy the menu

--- client/Environments/views/scene/environmentitemsview.coffee
+++ client/Environments/views/scene/environmentitemsview.coffee
@@ -49,8 +49,8 @@
       menuItems

     ctxMenu.on 'ContextMenuItemReceivedClick', (item) =>
-      {action} = item.getData()
-      ctxMenu.destroy()  if @["cm#{action}"]?
+      {action, callback} = item.getData()
+      ctxMenu.destroy()  if callback? or @["cm#{action}"]?
       @["cm#{action}"]?()

   # - Context Menu Actions - #


Client: Activitiy: Deleted unused channelid props.

--- client/activity/lib/components/channellistitem/index.coffee
+++ client/activity/lib/components/channellistitem/index.coffee
@@ -5,8 +5,7 @@
 module.exports = class ChannelListItem extends React.Component

   @defaultProps =
-    channel       : null
-    channelId     : null
+    channel  : null

   unfollowChannel: ->


sidebar: load followed public channels when component mounted

--- client/activity/lib/components/sidebarsections/index.coffee
+++ client/activity/lib/components/sidebarsections/index.coffee
@@ -8,7 +8,7 @@
 module.exports = class SidebarSections extends React.Component

-  { getters } = ActivityFlux
+  { getters, actions } = ActivityFlux

   getDataBindings: ->
     return {
@@ -16,6 +16,9 @@
       selectedThreadId: getters.selectedChannelThreadId
     }

+
+  componentDidMount: ->
+    actions.channel.loadFollowedPublicChannels()

   renderChannelsSection: ->
     <SidebarChannelsSection


navigation: item selection fix on main tab pane change

--- client/app/MainApp/maincontroller/mainviewcontroller.coffee
+++ client/app/MainApp/maincontroller/mainviewcontroller.coffee
@@ -41,7 +41,7 @@
     cdController.emit "ContentDisplaysShouldBeHidden"

     @setViewState pane.getOptions()
-    navController.selectItemByName name
+    navController.selectItemByName route.slice(1)

   isEntryPointSet = null


Grabbing cursor

Grabbing cursor

--- public/dreamcatcher/controllers/ib_dropbox_controller.coffee
+++ public/dreamcatcher/controllers/ib_dropbox_controller.coffee
@@ -24,6 +24,7 @@
     }

     $("#dropbox").draggable {
+      cursor: 'grabbing'
       handle: 'h2,.icon'
       stop: (ev, ui) =>
         @stateCookie.set ui.position
@@ -59,19 +60,13 @@
     el.draggable {
       containment: 'document'
       helper: 'clone'
+      cursor: 'grabbing' #todo
       zIndex: 100
       start: (ev, ui) =>
-        ###
-        #ev.target.className = 'grabbing'
-        #$("#dropbox .active").hide()
-        @isDragging = false
-        ev.target.removeClass 'grabbing'
-        ###
         if fromDropbox
           $("#dropbox").css('z-index','1100')
           $("#bodyClick").show()
       stop: (ev, ui) =>
-        #ui.draggable.removeClass 'grabbing'
         if fromDropbox
           $("#dropbox").css('z-index','')
           $("#bodyClick").hide()


add test for disabling check button while clicking save

--- common/lib/xmodule/xmodule/js/spec/capa/display_spec.coffee
+++ common/lib/xmodule/xmodule/js/spec/capa/display_spec.coffee
@@ -538,6 +538,19 @@
       runs ->
         expect(window.SR.readElts).toHaveBeenCalled()

+    it 'disables check button while posting', ->
+      runs ->
+        spyOn($, 'postWithPrefix').andCallFake (url, answers, callback) -> callback(success: 'OK')
+        spyOn @problem, 'enableCheckButton'
+        @problem.save()
+        expect(@problem.enableCheckButton).toHaveBeenCalledWith false
+      waitsFor (->
+        return jQuery.active == 0
+      ), "jQuery requests finished", 1000
+
+      runs ->
+        expect(@problem.enableCheckButton).toHaveBeenCalledWith true
+
   describe 'refreshMath', ->
     beforeEach ->
       @problem = new Problem($('.xblock-student_view'))


unit tests for locking timeouts

unit tests for locking timeouts

--- test/unit/coffee/LockManager/tryLockTests.coffee
+++ test/unit/coffee/LockManager/tryLockTests.coffee
@@ -19,7 +19,7 @@
 						key_schema:
 							blockingKey: ({doc_id}) -> "Blocking:#{doc_id}"
 			}
-			"./Profiler": class Profiler
+			"./Profiler": @Profiler = class Profiler
 				log: sinon.stub().returns { end: sinon.stub() }
 				end: sinon.stub()

@@ -48,3 +48,39 @@
 		it "should return the callback with false", ->
 			@callback.calledWith(null, false).should.equal true

+	describe "when it takes a long time for redis to set the lock", ->
+		beforeEach ->
+			@Profiler.prototype.end = () -> 7000 # take a long time
+			@Profiler.prototype.log = sinon.stub().returns { end: @Profiler.prototype.end }
+			@lockValue = "mock-lock-value"
+			@LockManager.randomLock = sinon.stub().returns @lockValue
+			@LockManager.releaseLock = sinon.stub().callsArgWith(2,null)
+			@set.callsArgWith(5, null, "OK")
+
+		describe "in all cases", ->
+			beforeEach ->
+				@LockManager.tryLock @doc_id, @callback
+
+			it "should set the lock key with an expiry if it is not set", ->
+				@set.calledWith("Blocking:#{@doc_id}", @lockValue, "EX", 30, "NX")
+					.should.equal true
+
+			it "should try to release the lock", ->
+				@LockManager.releaseLock.calledWith(@doc_id, @lockValue).should.equal true
+
+		describe "if the lock is released successfully", ->
+			beforeEach ->
+				@LockManager.releaseLock = sinon.stub().callsArgWith(2,null)
+				@LockManager.tryLock @doc_id, @callback
+
+			it "should return the callback with false", ->
+				@callback.calledWith(null, false).should.equal true
+
+		describe "if the lock has already timed out", ->
+			beforeEach ->
+				@LockManager.releaseLock = sinon.stub().callsArgWith(2, new Error("tried to release timed out lock"))
+				@LockManager.tryLock @doc_id, @callback
+
+			it "should return the callback with an error", ->
+				e = new Error("tried to release timed out lock")
+				@callback.calledWith(e).should.equal true


Index creation updated to accept options

--- server/src/Component/Builtin/DataSource/ElasticSearch.coffee
+++ server/src/Component/Builtin/DataSource/ElasticSearch.coffee
@@ -75,7 +75,7 @@
     indexExists: (dataSource, index, callback) ->
         @client(dataSource).indices.exists index : index, callback

-    createIndex: (dataSource, index, callback) ->
-        @client(dataSource).indices.create index : index, callback
+    createIndex: (dataSource, params, callback) ->
+        @client(dataSource).indices.create params, callback

 module.exports = ElasticSearch


CollectionView: defaultThumbnail is first shell's

--- coffee/src/shells/collection.shell.coffee
+++ coffee/src/shells/collection.shell.coffee
@@ -62,6 +62,10 @@
       @set 'shells', []

+  defaultThumbnail: =>
+    @shells().first()?.thumbnail() or super
+
+
   # lazily construct shells collection
   shells: =>
     unless @_shells


shellEditorView: remove redundant method calls

renderRemixerViewHeading gets called by renderUpdates, and renderUpdates
is called when a link changes and after all calls to renderRemixerView

--- coffee/src/views/shell_editor_view.coffee
+++ coffee/src/views/shell_editor_view.coffee
@@ -113,7 +113,6 @@
     else
       @$('.remix-views').append remixerView.el

-    @renderRemixerViewHeading(remixerView, index)
     @

   renderRemixerViewHeading: (remixerView, index) =>
@@ -241,7 +240,6 @@
       @swapSubShell oldShell, newShell

     view.on 'Remixer:LinkChanged', (remixer, newlink) =>
-      @renderRemixerViewHeading(remixer) if remixer
       @trigger 'ShellEditor:ShellsUpdated'

     view


Quick fix require statements.

Quick fix require statements.

--- services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
+++ services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
@@ -3,7 +3,7 @@
 	"ace/ace"
 	"libs/latex-log-parser"
 	"libs/bib-log-parser"
-], (App, LogParser, BibLogParser) ->
+], (App, Ace, LogParser, BibLogParser) ->
 	App.controller "PdfController", ($scope, $http, ide, $modal, synctex, event_tracking, localStorage) ->

 		# enable per-user containers if querystring includes isolated=true


Default chapter panels to be expanded

Default chapter panels to be expanded

--- src/components/learning-guide/chapter.cjsx
+++ src/components/learning-guide/chapter.cjsx
@@ -18,7 +18,7 @@
     onPractice: React.PropTypes.func

   getDefaultProps: ->
-    defaultExpanded: false
+    defaultExpanded: true # read by the BS.CollapsibleMixin

   onToggle: (event) ->
     @setState(expanded: not @state.expanded)


adapt ranges from worksheet to current definitions in GDocIndicatorImporter

--- server/lib/gdoc_indicator_importer.coffee
+++ server/lib/gdoc_indicator_importer.coffee
@@ -11,8 +11,8 @@
   ranges = []
   while (range = worksheet[index.toString()])?
     ranges.push(
-      threshold:  range['1'].value
-      text:  range['2'].value
+      minValue: parseFloat(range['1'].value, 10)
+      message: range['2'].value
     )
     index = index + 1


get clientId from req

get clientId from req

--- servers/lib/server/github_callback.coffee
+++ servers/lib/server/github_callback.coffee
@@ -14,8 +14,9 @@
     redirectOauth err, req, res, options

-fetchUserEmail = (req, res, clientId, userEmailResp, originalResp) ->
-  rawResp = ''
+fetchUserEmail = (req, res,  userEmailResp, originalResp) ->
+  { clientId }  = req.cookies
+  rawResp       = ''
   userEmailResp.on 'data', (chunk) -> rawResp += chunk
   userEmailResp.on 'end', ->
     try
@@ -105,7 +106,7 @@
           method  : 'GET'
           headers : headers
         r = http.request options, (newResp) ->
-          fetchUserEmail req, res, clientId, newResp, resp
+          fetchUserEmail req, res,  newResp, resp
         r.end()
       else
         saveOauthAndRedirect resp, res, clientId, req


Use correct argument name

Use correct argument name

--- src/file.coffee
+++ src/file.coffee
@@ -188,7 +188,7 @@
   writeFileSync: (filePath, contents) ->
     encoding = @getEncoding()
     if encoding is 'utf8'
-      fs.writeFileSync(filePath, text, {encoding})
+      fs.writeFileSync(filePath, contents, {encoding})
     else
       iconv ?= require 'iconv-lite'
       fs.writeFileSync(filePath, iconv.encode(contents, encoding))


fixed issues with change event from advice.

--- src/server/advice.coffee
+++ src/server/advice.coffee
@@ -88,12 +88,12 @@
                         attrName : attr.name
                         newValue : attr.value
                         attrChange : type
-                    if /input|textarea|select/.test(target.tagName?.toLowerCase())
-                        process.nextTick () ->
-                            doc = target._ownerDocument
-                            ev = doc.createEvent('HTMLEvents')
-                            ev.initEvent('change', false, false)
-                            target.dispatchEvent(ev)
+                if /input|textarea|select/.test(target.tagName?.toLowerCase())
+                    process.nextTick () ->
+                        doc = target._ownerDocument
+                        ev = doc.createEvent('HTMLEvents')
+                        ev.initEvent('change', false, false)
+                        target.dispatchEvent(ev)
         # setNamedItem(node)
         adviseMethod html.AttrNodeMap,
                           'setNamedItem',
@@ -109,6 +109,11 @@
     # selection won't actually be changed.
     adviseProperty html.HTMLOptionElement, 'selected',
         setter : (elem, value) ->
+            process.nextTick () ->
+                doc = elem._ownerDocument
+                ev = doc.createEvent('HTMLEvents')
+                ev.initEvent('change', false, false)
+                elem.dispatchEvent(ev)
             if elem._attachedToDocument
                 emitter.emit 'DOMPropertyModified',
                     target   : elem


Attempting (and failing) to fix pagination

--- resources/assets/scripts/app/directives/paged-tracks-list.coffee
+++ resources/assets/scripts/app/directives/paged-tracks-list.coffee
@@ -23,8 +23,8 @@
         class: '@class'

     controller: [
-        '$scope', 'tracks', '$state',
-        ($scope, tracks, $state) ->
+        '$scope', 'tracks', '$state', '$location'
+        ($scope, tracks, $state, $location) ->
             typeEnum = switch
                 when $scope.type == 'normal' then tracks.FetchType.NORMAL
                 when $scope.type == 'all' then tracks.FetchType.ALL
@@ -58,10 +58,16 @@
                 # The actual last page will always be in the paginator.
                 $scope.pages.push($scope.totalPages) unless $scope.totalPages in $scope.pages

+                console.log $state
+
             $scope.pageSelectorShown = false

             $scope.gotoPage = (page) ->
-                $state.transitionTo $state.current.name, {filter: $state.params.filter, page: page}
+                #$state.transitionTo $state.current.name, {filter: $state.params.filter, page: page}
+                #$location.search 'filter', $state.params.filter
+                #$location.search 'page', page
+                tracks.mainQuery.fromFilterString($state.params.filter)
+                tracks.mainQuery.setPage page || 1

             $scope.showPageSelector = () ->
                 $scope.pageSelectorShown = true


Fixed issue #1571 - Improve i-doit filtering (without type).

--- app/assets/javascripts/app/controllers/idoit_object_selector.coffee
+++ app/assets/javascripts/app/controllers/idoit_object_selector.coffee
@@ -44,6 +44,8 @@
     ''

   search: (filter) =>
+    if _.isEmpty(filter.type)
+      delete filter.type
     if _.isEmpty(filter.title)
       delete filter.title
     else


Fixes select-all checkbox callback with proper event target

--- app/assets/javascripts/georgia/components/tables/checkbox.js.coffee
+++ app/assets/javascripts/georgia/components/tables/checkbox.js.coffee
@@ -12,7 +12,7 @@
     @element.on('click', @checkboxes(), @update)

   update: (event) =>
-    checkbox = $(event.currentTarget)
+    checkbox = $(event.target)
     @updateCheckbox(checkbox)
     @updateSelectAll()


ah yes

ah yes

--- evilbot.coffee
+++ evilbot.coffee
@@ -95,7 +95,9 @@

 listen = (cursor) ->
   url = '/api/live.json'
-  url = "#{url}?cursor=#{cursor}" if cursor
+
+  if cursor and cursor.constructor == String
+    url += "?cursor=#{cursor}"

   get url, (body) ->
     for message in body.messages


- [grunt] specified Sass cache location

- [grunt] specified Sass cache location

--- tasks/grunt-contrib-sass.coffee
+++ tasks/grunt-contrib-sass.coffee
@@ -9,6 +9,7 @@
       options:
         style: 'nested'
         loadPath: '.'
+        cacheLocation: '<%= path.temp.styles %>/.sass-cache'
       files: [
         src: '<%= file.source.style %>'
         dest: '<%= file.build.style.compiled %>'