chore(web): Remove preferOnline

chore(web): Remove preferOnline

--- apps/actor-web/gulpfile.coffee
+++ apps/actor-web/gulpfile.coffee
@@ -111,7 +111,6 @@
   gulp.src ['./dist/**/*']
     .pipe manifest {
       hash: true,
-      preferOnline: true,
       network: ['http://*', 'https://*', '*'],
       filename: 'app.manifest',
       exclude: 'app.manifest'


Use atom.comands.add instead of .on

Use atom.comands.add instead of .on

--- lib/encoding-list-view.coffee
+++ lib/encoding-list-view.coffee
@@ -13,9 +13,9 @@

     @currentEncoding = @editor.getEncoding()

-    @on 'encoding-selector:show', =>
+    @commandSubscription = atom.commands.add @element, 'encoding-selector:show', (event) =>
       @cancel()
-      false
+      event.stopPropagation()

     encodingItems = []

@@ -28,6 +28,9 @@

   getFilterKey: ->
     'name'
+
+  beforeRemove: ->
+    @commandSubscription.dispose()

   viewForItem: (encoding) ->
     element = document.createElement('li')


minor

minor

--- client/landing/app/Applications/Account.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Account.kdapplication/AppController.coffee
@@ -70,13 +70,12 @@
       """

     # SET UP SPLIT VIEW AND TOGGLERS
-    @split = split = new SplitView
+    mainView.addSubView @split = split = new SplitView
       domId     : "account-split-view"
-      sizes     : [188,null]
-      views     : [navView,wrapperView]
-      minimums  : [null,null]
+      sizes     : [188, null]
+      views     : [navView, wrapperView]
+      minimums  : [null, null]
       resizable : yes
-    mainView.addSubView split

     [panel0, panel1] = split.panels


[memberlist][graph] changed member list fetching query to fetch from neo4j

--- client/landing/app/Applications/Members.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Members.kdapplication/AppController.coffee
@@ -41,25 +41,15 @@
               JAccount.byRelevance @_searchValue, options, callback
             else
               group = KD.getSingleton('groupsController').getCurrentGroup()
-              if group?
-                relationshipOptions = {
-                  targetOptions: {options}
-                }
-                selector = {}
-                group.fetchMembers selector, relationshipOptions, callback
-                # group.countMembers selector, (err, count)=>
-                #   @setCurrentViewNumber 'all', count
-              else
-                JAccount.someWithRelationship selector, options, callback
-                JAccount.count selector, (err, count)=>
-                  @setCurrentViewNumber 'all', count
+              group.fetchMembersFromGraph options, callback
+
         followed            :
           loggedInOnly      : yes
           title             : "Followers "
           noItemFoundText   : "There is no member who follows you."
           dataSource        : (selector, options, callback)=>
-            KD.whoami().fetchFollowersWithRelationship selector, options, (err, items, rest...)=>
-              callback err, items, rest...
+            options.groupId or= KD.getSingleton('groupsController').getCurrentGroup().getId()
+            KD.whoami().fetchMyFollowersFromGraph options, callback

             KD.whoami().countFollowersWithRelationship selector, (err, count)=>
               @setCurrentViewNumber 'followers', count
@@ -68,8 +58,8 @@
           title             : "Following "
           noItemFoundText   : "You are not following anyone."
           dataSource        : (selector, options, callback)=>
-            KD.whoami().fetchFollowingWithRelationship selector, options, (err, items, rest...)=>
-              callback err, items, rest...
+            options.groupId or= KD.getSingleton('groupsController').getCurrentGroup().getId()
+            KD.whoami().fetchMyFollowingsFromGraph options, callback

             KD.whoami().countFollowingWithRelationship selector, (err, count)=>
               @setCurrentViewNumber 'following', count


Show group VMs only.

Show group VMs only.

--- client/landing/app/MainApp/filetree/controllers/resourcescontroller.coffee
+++ client/landing/app/MainApp/filetree/controllers/resourcescontroller.coffee
@@ -15,7 +15,7 @@

   reset:->
     KD.singletons.vmController.resetVMData()
-    KD.singletons.vmController.fetchVMs (err, vms)=>
+    KD.singletons.vmController.fetchGroupVMs (err, vms)=>
       @removeAllItems()
       @instantiateListItems vms  unless err
       @deselectAllItems()


TextEditorElement: Add callback for TextEditor::onDidChangeMini

--- src/text-editor-element.coffee
+++ src/text-editor-element.coffee
@@ -83,11 +83,12 @@
     @model = model
     @mountComponent()
     @addGrammarScopeAttribute()
-    @addMiniAttributeIfNeeded()
+    @addMiniAttribute() if @model.isMini()
     @addEncodingAttribute()
     @model.onDidChangeGrammar => @addGrammarScopeAttribute()
     @model.onDidChangeEncoding => @addEncodingAttribute()
     @model.onDidDestroy => @unmountComponent()
+    @model.onDidChangeMini (mini) => if mini then @addMiniAttribute() else @removeMiniAttribute()
     @__spacePenView.setModel(@model) if Grim.includeDeprecatedAPIs
     @model

@@ -155,8 +156,11 @@
     grammarScope = @model.getGrammar()?.scopeName?.replace(/\./g, ' ')
     @dataset.grammar = grammarScope

-  addMiniAttributeIfNeeded: ->
-    @setAttributeNode(document.createAttribute("mini")) if @model.isMini()
+  addMiniAttribute: ->
+    @setAttributeNode(document.createAttribute("mini"))
+
+  removeMiniAttribute: ->
+    @removeAttribute("mini")

   addEncodingAttribute: ->
     @dataset.encoding = @model.getEncoding()


Make V1.Backbone.js play nicer with the V1 stack (namely Bootstrap.js)

--- V1.Backbone.coffee
+++ V1.Backbone.coffee
@@ -1,6 +1,6 @@
-V1 = if exports? then exports else (window.V1 ||= {})
-_ = if !window?._? then (if require? then require('underscore') else throw "Unable to load/find underscore") else window._
-Backbone = if !window?.Backbone? then (if require? then require('Backbone') else throw "Unable to load/find backbone") else window.Backbone
+V1 = if exports? then exports else this.V1 or (this.V1 = {})
+_ = if !this?._? then (if require? then require('underscore') else throw "Unable to load/find underscore") else this._
+Backbone = if !this?.Backbone? then (if require? then require('Backbone') else throw "Unable to load/find backbone") else this.Backbone

 defaultRetriever = undefined
 defaultPersister = undefined


bugfix: dont extend resource with data if data isnt a object

--- src/jt-resource-factory.coffee
+++ src/jt-resource-factory.coffee
@@ -106,7 +106,7 @@
               delete resource[k]

         resource.$data = data
-        extendResourceWithData(data)
+        extendResourceWithData(data) if angular.isObject(data)
         deferredPromise.resolve(data)
         deferredNetworkPromise.resolve(data)

@@ -126,7 +126,7 @@

         dataFromCache = transformCacheAfter(angular.copy(cacheValue))

-        extendResourceWithData(dataFromCache)
+        extendResourceWithData(dataFromCache) if angular.isObject(dataFromCache)
         deferredPromise.resolve(dataFromCache)

       createRequestToServer = ->


Client/Sidebar: refactor route build method

--- client/Social/Activity/sidebar/topicitem.coffee
+++ client/Social/Activity/sidebar/topicitem.coffee
@@ -4,8 +4,14 @@

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

-    {name}                  = data
-    options.route           = "Topic/#{name}"
+    {name, typeConstant}    = data
+
+    # rewrite grop channel as topic?
+    typeConstant            = "topic" if typeConstant is "group"
+    # uppercase first letter of the type constant for route
+    typeConstant            = "#{typeConstant.charAt(0).toUpperCase()}#{typeConstant.slice 1}"
+    # build route with typeConstant and name
+    options.route           = "#{typeConstant}/#{name}"
     options.cssClass        = 'clearfix'

     super options, data


resurrection.activity: hide input if user is not logged in

--- client/Social/Activity/views/inputwidget.coffee
+++ client/Social/Activity/views/inputwidget.coffee
@@ -101,3 +101,4 @@
     @addSubView @input
     @addSubView @embedBox
     @input.addSubView @submit
+    @hide()  unless KD.isLoggedIn()


Fix broken indentation

Fix broken indentation

--- client/account/lib/accountcredentiallist.coffee
+++ client/account/lib/accountcredentiallist.coffee
@@ -81,32 +81,32 @@
       removeButtonTitle = 'Remove Access'

     modal              = new KDModalView
-        title          : 'Remove credential'
-        content        : "
#{description}
"
-        cssClass       : 'has-markdown remove-credential'
-        overlay        : yes
-        overlayOptions :
-          cssClass     : 'second-overlay'
-          overlayClick : yes
-        buttons        :
-          Remove       :
-            title      : removeButtonTitle ? 'Remove Credential'
-            style      : 'solid red medium'
-            loader     : yes
-            callback   : ->
-              callback { action : 'Remove', modal }
-          DestroyAll   :
-            title      : 'Destroy Everything'
-            style      : "solid red medium #{unless bootstrapped then 'hidden'}"
-            loader     : yes
-            callback   : ->
-              callback { action : 'DestroyAll', modal }
-          cancel       :
-            title      : 'Cancel'
-            style      : 'solid light-gray medium'
-            callback   : ->
-              modal.destroy()
-              callback { action : 'Cancel', modal }
+      title          : 'Remove credential'
+      content        : "
#{description}
"
+      cssClass       : 'has-markdown remove-credential'
+      overlay        : yes
+      overlayOptions :
+        cssClass     : 'second-overlay'
+        overlayClick : yes
+      buttons        :
+        Remove       :
+          title      : removeButtonTitle ? 'Remove Credential'
+          style      : 'solid red medium'
+          loader     : yes
+          callback   : ->
+            callback { action : 'Remove', modal }
+        DestroyAll   :
+          title      : 'Destroy Everything'
+          style      : "solid red medium #{unless bootstrapped then 'hidden'}"
+          loader     : yes
+          callback   : ->
+            callback { action : 'DestroyAll', modal }
+        cancel       :
+          title      : 'Cancel'
+          style      : 'solid light-gray medium'
+          callback   : ->
+            modal.destroy()
+            callback { action : 'Cancel', modal }

   showCredentialEditModal: (options = {}) ->


activity/flux: dispatch messages load succes after all individuals

--- client/activity/lib/flux/actions/message.coffee
+++ client/activity/lib/flux/actions/message.coffee
@@ -32,11 +32,11 @@
         dispatch LOAD_MESSAGES_FAIL, { err, channelId }
         return reject err

-      dispatch LOAD_MESSAGES_SUCCESS, { channelId }

       kd.singletons.reactor.batch ->
         messages.forEach (message) ->
           dispatchLoadMessageSuccess channelId, message
+        dispatch LOAD_MESSAGES_SUCCESS, { channelId, messages }

       resolve { messages }


Client: Activity: Added jsdoc

Client: Activity: Added jsdoc

--- client/activity/lib/flux/actions/message.coffee
+++ client/activity/lib/flux/actions/message.coffee
@@ -165,6 +165,7 @@
  *
  * @param {string} messageId
  * @param {string} body
+ * @param {string} createdAt
  * @param {object=} payload
 ###
 editMessage = (messageId, body, createdAt, payload = {}) ->


ManagedHelpers: update data after kite fetch, so we don't need to process it everytime

--- client/app/lib/providers/managed/helpers.coffee
+++ client/app/lib/providers/managed/helpers.coffee
@@ -12,12 +12,27 @@

   queryKites: ->

-    return kd.singletons.kontrol
+    { generateQueryString } = require 'app/kite/kitecache'
+    { computeController, kontrol } = kd.singletons
+
+    return kontrol
       .queryKites
         query         :
           username    : nick()
           environment : 'managed'
       .timeout 5000
+      .then (result)->
+
+        if result?.kites?.length
+          {kites} = result
+          kites.forEach (kite)->
+            kite.queryString = generateQueryString kite.kite
+            kite.machine     = computeController
+              .findMachineFromQueryString kite.queryString
+            kite.ipAddress   = getIp kite.url
+          return kites
+        else
+          return []

   createMachine: (kite, callback)->
@@ -26,11 +41,9 @@

     stack = computeController.stacks.first._id

-    { generateQueryString } = require 'app/kite/kitecache'
-
     computeController.create {
       provider    : 'managed'
-      queryString : generateQueryString kite.kite
-      ipAddress   : getIp kite.url
+      queryString : kite.queryString
+      ipAddress   : kite.ipAddress
       stack
     }, callback


Cleanup: mibor

Cleanup: mibor

--- client/ide/filetree/itemviews/finderitem.coffee
+++ client/ide/filetree/itemviews/finderitem.coffee
@@ -53,10 +53,10 @@
       delete @progressView

     @childView.show()
-    @beingDeleted = no
-    @beingEdited = no
+    @beingDeleted  = no
+    @beingEdited   = no
     @beingProgress = no
-    @callback = null
+    @callback      = null
     @unsetClass "being-deleted being-edited progress"
     @getDelegate().setKeyView()


Recognize es2015 on browser too

Recognize es2015 on browser too

--- src/lib/ComponentLoader.coffee
+++ src/lib/ComponentLoader.coffee
@@ -219,7 +219,7 @@
           bare: true
       catch e
         return callback e
-    else if language is 'es6'
+    else if language in ['es6', 'es2015']
       unless window.babel
         return callback new Error 'Babel compiler not available'
       try


Ember Model sets primaryKey on class

Ember Model sets primaryKey on class

--- assets/scripts/app/models/account.coffee
+++ assets/scripts/app/models/account.coffee
@@ -1,7 +1,6 @@
 require 'travis/model'

 @Travis.Account = Travis.Model.extend
-  primaryKey: 'login'
   login:       Ember.attr('string')
   name:        Ember.attr('string')
   type:        Ember.attr('string')
@@ -15,3 +14,5 @@
   reposCount: (->
     @get('_reposCount') || 0
   ).property('_reposCount')
+
+Travis.Account.primaryKey = 'login'


set presence on every connection

set presence on every connection

--- src/ui/views/controller.coffee
+++ src/ui/views/controller.coffee
@@ -15,10 +15,9 @@
         # draw view
         conninfo connection

-        console.log 'Updating connection'
-
         # place in layout
         if connection.state == connection.CONNECTED
+            later -> action 'lastActivity'
             el?.hide?()
             el = null
         else if viewstate.state != viewstate.STATE_STARTUP


activity: switch to most recent filter when a post is submitted [fixes #78579384]

--- client/Social/Activity/views/messagepane.coffee
+++ client/Social/Activity/views/messagepane.coffee
@@ -90,7 +90,10 @@
     return  unless value

     @input.reset yes
-    @createFakeItemView value, clientRequestId
+
+    switch @currentFilter
+      when 'Most Liked' then @setFilter 'Most Recent'
+      else @createFakeItemView value, clientRequestId

   putMessage: (message, index = 0) -> @appendMessage message, index


Client: Sidebar: Feed: Renamed channel action to setChannelResultStateFlag

--- client/activity/lib/flux/actions/channel.coffee
+++ client/activity/lib/flux/actions/channel.coffee
@@ -518,11 +518,11 @@
     dispatch UPDATE_CHANNEL_SUCCESS, { channel }

-setShowPopularMessagesFlag = (showPopularMessagesFlag) ->
-
-  { SET_SHOW_POPULAR_MESSAGES_FLAG } = actionTypes
-
-  dispatch SET_SHOW_POPULAR_MESSAGES_FLAG, { showPopularMessagesFlag }
+setChannelResultStateFlag = (channelId, resultState) ->
+
+  { SET_CHANNEL_RESULT_STATE_FLAG } = actionTypes
+
+  dispatch SET_CHANNEL_RESULT_STATE_FLAG, { resultState, channelId }

 module.exports = {
@@ -550,6 +550,6 @@
   setScrollPosition
   setLastSeenTime
   updateChannel
-  setShowPopularMessagesFlag
+  setChannelResultStateFlag
 }


Main: removed unused logic for navigation item selection

--- client/Main/maincontroller/mainviewcontroller.coffee
+++ client/Main/maincontroller/mainviewcontroller.coffee
@@ -102,12 +102,6 @@
     then @setViewState pane.getOptions()
     else mainTabView.getActivePane().show()

-    {title} = app?.getOption('navItem')
-
-    if title
-    then navController.selectItemByName title
-    else navController.deselectAllItems()
-

   setViewState: (options = {}) ->


Revert "Social/Client: disable commenting for announcement posts"

This reverts commit 75e47ff3bf4d325ee882d5b22f325a6579197d32.

Conflicts:
	client/Social/Activity/views/activitylistitem.coffee

--- client/Social/Activity/views/activitylistitem.coffee
+++ client/Social/Activity/views/activitylistitem.coffee
@@ -44,10 +44,6 @@
     {socialapi} = KD.singletons

     @commentBox  = new commentViewClass options.commentSettings, data
-
-    # hide commenting for announcement items
-    if socialapi.isAnnouncementItem data.initialChannelId
-      @commentBox.hide()

     @actionLinks = new ActivityActionsView delegate: @commentBox, data


DefineStackView: remove not needed fat-arrow

--- client/admin/lib/views/stacks/definestackview.coffee
+++ client/admin/lib/views/stacks/definestackview.coffee
@@ -99,7 +99,7 @@
         @providersView.resetItems()
         credentialItem.inuseView.show()

-    @providersView.on 'ItemDeleted', (credential) =>
+    @providersView.on 'ItemDeleted', (credential) ->

       { identifier } = credential.getData()
       if identifier in credentialStatus.credentials


Fix set default font issue in Ace

Fix set default font issue in Ace

--- client/app/Applications/Ace.kdapplication/ace.coffee
+++ client/app/Applications/Ace.kdapplication/ace.coffee
@@ -124,7 +124,7 @@
     @appStorage.getValue('showInvisibles') or @editor.getShowInvisibles()

   getFontSize:->
-    @appStorage.getValue('fontSize') or parseInt(@$("#editor#{@getId()}").css("font-size"), 12)
+    @appStorage.getValue('fontSize') or parseInt @$("#editor#{@getId()}").css("font-size") ? 12, 10

   getTabSize:->
     @appStorage.getValue('tabSize') or @editor.getSession().getTabSize()


client: fix generateCollaborationLink args problem

--- client/app/lib/util/generateCollaborationLink.coffee
+++ client/app/lib/util/generateCollaborationLink.coffee
@@ -1,6 +1,8 @@
-module.exports = generateCollaborationLink = (nickname, channelId, { isAbsolute = yes }) ->
+module.exports = generateCollaborationLink = (nickname, channelId, options = {}) ->

   {origin} = global.location
+
+  isAbsolute = options.isAbsolute ? yes

   return "#{if isAbsolute then origin else ''}/Collaboration/#{nickname}/#{channelId}"


Register finderPane as an instance member.

--- client/ide/views/tabview/idefilestabview.coffee
+++ client/ide/views/tabview/idefilestabview.coffee
@@ -11,7 +11,9 @@
       name     : 'Files'
       closable : no

-    filesPane.addSubView new FinderPane
+    @finderPane = new FinderPane
+    filesPane.addSubView @finderPane
+
     @tabView.addPane filesPane

     vmsPane    = new KDTabPaneView


Index switching feature integrated

Index switching feature integrated

A lot of refactoring, optimizations, index switching option, Some bug
fixes with the scrollbar and Firefox. More comments.

--- src/jquery.rondell.coffee
+++ src/jquery.rondell.coffee
@@ -47,6 +47,7 @@
           height: 0
       repeating: true           # Will show first item after last item and so on
       wrapIndices: true         # Will modify relative item indices to fix positioning when repeating
+      switchIndices: false      # After a shift the last focused item and the new one will switch indices
       alwaysShowCaption: false  # Don't hide caption on mouseleave
       autoRotation:             # If the cursor leaves the rondell continue spinning
         enabled: false
@@ -119,7 +120,7 @@
       @maxItems = numItems
       @loadedItems = 0
       @initCallback = initCallback
-
+
       # Update rondell properties with new options
       if options?.preset of $.rondell.presets
         $.extend(true, @, $.rondell.defaults, $.rondell.presets[options.preset], options or {})
@@ -129,6 +130,8 @@
       # Init some private variables
       $.extend true, @,
         _lastKeyEvent: 0
+        _focusedIndex: @currentLayer
+        _itemIndices: {}
         autoRotation:
           _timer: -1
         controls:
@@ -179,28 +182,32 @@
     funcSize: (layerDist, rondell, idx) ->
       1

-    showCaption: (layerNum) =>
+    showCaption: (idx) =>
       # Restore automatic height and show caption
-      $('.rondellCaption.overlay', @_getItem(layerNum).object)
-      .css(
+      @_getItem(idx).overlay?.stop(true).css
         height: 'auto'
         overflow: 'auto'
-      ).stop(true).fadeTo(300, 1)
-
-    hideCaption: (layerNum) =>
+      .fadeTo 300, 1
+
+    hideCaption: (idx) =>
       # Fix height before hiding the caption to avoid jumping text when the item changes its size
-      caption = $('.rondellCaption.overlay:visible', @_getItem(layerNum).object)
-      caption.css(
-        height: caption.height()
+      overlay = @_getItem(idx).overlay
+      overlay?.filter(":visible").stop(true).css
+        height: overlay.height()
         overflow: 'hidden'
-      ).stop(true).fadeTo(200, 0)
+      .fadeTo 200, 0

     _getItem: (layerNum) =>
       @items[layerNum - 1]

     _initItem: (layerNum, item) =>
       @items[layerNum - 1] = item
-
+
+      # Add some private variables
+      item.id = layerNum
+      item.animating = false
+      @_itemIndices[layerNum] = item.currentSlot = layerNum
+
       # If item is an img tag, wrap with div
       if item.object[0].nodeName.toLowerCase() is "img"
         item.object.wrap("
")
@@ -221,13 +228,11 @@

       # Create overlay from caption if found
       if captionContent?.length
-        captionContainer = $('

')
-        captionContainer.addClass('overlay') if item.icon
-        captionContent.wrapAll(captionContainer)
-
-      # Add some private variables
-      item.animating = false
-
+        captionContent.wrapAll $('

')
+
+        if item.icon
+          item.overlay = $(".rondellCaption", item.object).addClass('overlay')
+
       # Init click events
       item.object
       .addClass("rondellItemNew #{@itemProperties.cssClass}")
@@ -237,30 +242,30 @@
         height: item.sizeSmall.height
         left: @center.left - item.sizeFocused.width / 2
         top: @center.top - item.sizeFocused.height / 2
-      .bind('mouseenter mouseleave click', (e) =>
+      .bind 'mouseenter mouseleave click', (e) =>
         switch e.type
-          when 'mouseenter' then @_onMouseEnterItem layerNum
-          when 'mouseleave' then @_onMouseLeaveItem layerNum
+          when 'mouseenter' then @_onMouseEnterItem item.id
+          when 'mouseleave' then @_onMouseLeaveItem item.id
           when 'click'
-            e.preventDefault() if @currentLayer isnt layerNum
-            @shiftTo(layerNum) if item.object.is(':visible') and not item.hidden
-            return false
-      )
+            if @_focusedItem.id isnt item.id
+              e.preventDefault()
+              if not item.hidden and item.object.is(':visible')
+                @shiftTo item.currentSlot

       @loadedItems += 1

       @_start() if @loadedItems is @maxItems

-    _onMouseEnterItem: (itemIndex) =>
-      item = @_getItem(itemIndex)
+    _onMouseEnterItem: (idx) =>
+      item = @_getItem idx
       if not item.animating and not item.hidden and item.object.is(':visible')
-        item.object.addClass('rondellItemHovered').stop(true).animate
+        item.object.addClass("rondellItemHovered").stop(true).animate
             opacity: 1
           , @fadeTime, @funcEase

-    _onMouseLeaveItem: (itemIndex) =>
-      item = @_getItem(itemIndex)
-      item.object.removeClass('rondellItemHovered')
+    _onMouseLeaveItem: (idx) =>
+      item = @_getItem idx
+      item.object.removeClass "rondellItemHovered"

       if not item.animating and not item.hidden
         item.object.stop(true).animate
@@ -388,6 +393,7 @@
       @initCallback?(@)

       # Move items to starting positions
+      @_focusedItem ?= @_getItem @currentLayer
       @shiftTo @currentLayer

     _onMobile: ->
@@ -440,7 +446,7 @@
             if Math.abs(changeX) > @touch.threshold
               if changeX > 0
                 @shiftLeft()
-              if changeX < 0
+              else if changeX < 0
                 @shiftRight()

             # Reset end position
@@ -464,19 +470,21 @@
         @hovering = true
         unless paused
           @autoRotation.paused = true
-          @showCaption(@currentLayer)
+          @showCaption(@_focusedItem.id)
       else
         @hovering = false
         if paused and not @autoRotation.once
           @autoRotation.paused = false
           @_autoShiftInit()
-        @hideCaption(@currentLayer) unless @alwaysShowCaption
+        @hideCaption(@_focusedItem.id) unless @alwaysShowCaption

       # Show or hide controls if they exist
       @_refreshControls() if @controls.enabled

     layerFadeIn: (layerNum) =>
       item = @_getItem(layerNum)
+      @_focusedItem = item
+
       item.small = false
       itemFocusedWidth = item.sizeFocused.width
       itemFocusedHeight = item.sizeFocused.height
@@ -511,14 +519,15 @@

     layerFadeOut: (layerNum) =>
       item = @_getItem(layerNum)
-
-      layerDist = Math.abs(layerNum - @currentLayer)
-      layerPos = layerNum
-
-      # Find new layer position if rondell is repeating and indices are wrapped
-      if layerDist > @visibleItems and layerDist > @maxItems / 2 and @repeating and @wrapIndices
-        if layerNum > @currentLayer then layerPos -= @maxItems else layerPos += @maxItems
-        layerDist = Math.abs(layerPos - @currentLayer)
+
+      # Make sure the items caption is hidden
+      @hideCaption layerNum
+
+      # Replace the items index with the actual slot the item is in
+      layerNum = item.currentSlot
+
+      # Get the distance and relative index in relation to the focused element
+      [layerDist, layerPos] = @getRelativeItemPosition layerNum

       # Get the absolute layer number difference
       layerDiff = @funcDiff(layerPos - @currentLayer, @, layerNum)
@@ -531,6 +540,7 @@
       # Modify fading time by items distance to focused item
       fadeTime = @fadeTime + @itemProperties.delay * layerDist

+      # Get new target for animation
       newTarget =
         width: itemWidth
         height: itemHeight
@@ -540,7 +550,6 @@

       # Smooth animation when item is visible
       if layerDist <= @visibleItems
-        @hideCaption layerNum

         newTarget.opacity = @funcOpacity layerDiff, @, layerNum

@@ -553,14 +562,16 @@
           and lastTarget.top is newTarget.top \
           and lastTarget.opacity is newTarget.opacity

-        item.lastTarget = newTarget
         item.animating = true
-
+        item.hidden = false
+
+        # Move item to it's new target
         item.object.removeClass("rondellItemNew rondellItemFocused").stop(true)
         .css
           zIndex: newZ
           display: "block"
         .animate newTarget, fadeTime, @funcEase, =>
+          # Hide item if it isn't visible anymore
           item.animating = false
           if newTarget.opacity < @opacityMin
             item.hidden = true
@@ -568,8 +579,9 @@
           else
             item.hidden = false
             item.object.css "display", "block"
-
-        item.hidden = false
+
+
+        # Recenter icon if it's now resizeable
         unless item.small
           item.small = true
           if item.icon and not item.resizeable
@@ -591,25 +603,52 @@
         .css('z-index', newZ)
         .animate newTarget, fadeTime / 2, @funcEase, =>
           item.animating = false
-          @hideCaption layerNum
-
+
+      # Store last target for this item
       item.lastTarget = newTarget

     shiftTo: (layerNum) =>
-      if @repeating
-        # Update current layer number if carousel reached it's limit
-        if layerNum < 1
-          layerNum = @maxItems
-        else if layerNum > @maxItems
-          layerNum = 1
-
-      if layerNum > 0 and layerNum <= @maxItems
-        @currentLayer = layerNum
-
-        # Hide all layers except the current layer
-        @layerFadeOut(i) for i in [1..@maxItems] when i isnt @currentLayer
-        @layerFadeIn(@currentLayer)
-
+      return unless layerNum
+
+      # Modify layer number when using index switch because
+      # we have to ignore the slot which was initially focused
+      if @switchIndices and layerNum isnt @currentLayer and @getIndexInRange(layerNum) is @_focusedItem.currentSlot
+        # Get items relative distance
+        [distance, relativeIndex] = @getRelativeItemPosition layerNum, true
+        if relativeIndex > @currentLayer
+          layerNum++
+        else
+          layerNum--
+
+      # Fix new layer number depending on the repeating option
+      layerNum = @getIndexInRange layerNum
+
+      # Get the items id in the selected layer slot
+      newItemIndex = @_itemIndices[layerNum]
+
+      # Switch item indices if flag is set
+      if @switchIndices
+        newItem = @_getItem newItemIndex
+
+        # Switch indices in list
+        @_itemIndices[layerNum] = @_focusedItem.id
+        @_itemIndices[@_focusedItem.currentSlot] = newItemIndex
+
+        # Tell items about their new slots
+        newItem.currentSlot = @_focusedItem.currentSlot
+        @_focusedItem.currentSlot = layerNum
+
+        # Set new focused item
+        @_focusedItem = newItem
+
+      # Store the now active layer index
+      @currentLayer = layerNum
+
+      # Move all items out of focus except the one we want to focus
+      @layerFadeOut(i) for i in [1..@maxItems] when i isnt newItemIndex
+      @layerFadeIn newItemIndex
+
+      # Update buttons e.g. fadein/out
       @_refreshControls()

       # Fire shift callback
@@ -617,10 +656,37 @@

       # Update scrollbar
       @scrollbar._instance.setPosition(layerNum, false) if @scrollbar.enabled
+
+    getRelativeItemPosition: (idx, wrapIndices=@wrapIndices) =>
+      distance = Math.abs(idx - @currentLayer)
+      relativeIndex = idx
+
+      # Find new layer position if rondell is repeating and indices are wrapped
+      if distance > @visibleItems and distance > @maxItems / 2 and @repeating and wrapIndices
+        if idx > @currentLayer
+          relativeIndex -= @maxItems
+        else
+          relativeIndex += @maxItems
+        distance = Math.abs(relativeIndex - @currentLayer)
+
+      [distance, relativeIndex]
+
+    getIndexInRange: (idx) =>
+      if @repeating
+        if idx < 1
+          idx += @maxItems
+        else if idx > @maxItems
+          idx -= @maxItems
+      else
+        if idx < 1
+          idx = 1
+        else if idx > @maxItems
+          idx = @maxItems
+      idx

     _refreshControls: =>
       return unless @controls.enabled
-
+
       @controls._shiftLeft.stop().fadeTo(@controls.fadeTime, if (@currentLayer > 1 or @repeating) and @hovering then 1 else 0)
       @controls._shiftRight.stop().fadeTo(@controls.fadeTime, if (@currentLayer < @maxItems or @repeating) and @hovering then 1 else 0)

@@ -708,7 +774,7 @@
       @container.append @scrollBackground, @scrollLeftControl, @scrollRightControl, @scrollControl

     updatePosition: (position, fireCallback=true) =>
-      return if position < @start or position > @end or position is @position
+      return if not position or position is @position or position < @start or position > @end

       @position = position

@@ -718,11 +784,12 @@
     scrollTo: (x, animate=true, fireCallback=true) =>
       return if x < @_minX or x > @_maxX

+      scroller = @scrollControl.stop true
       target = { left: x }
       if animate
-        @scrollControl.stop(true).animate target, @animationDuration, @easing
+        scroller.animate target, @animationDuration, @easing
       else
-        @scrollControl.stop(true).css target
+        scroller.css target

       # Translate event coordinates to new position between start and end option
       newPosition = Math.round((x - @_minX) / (@_maxX - @_minX) * (@end - @start)) + @start
@@ -763,9 +830,11 @@
         # Start drag event
         @_drag._dragging = true
         @scrollControl.addClass "rondell-scrollbar-dragging"
-      else if e.type is "click" and e.target is @scrollBackground[0] or e.target is @container[0]
+      else if e.type is "click" and (e.target is @scrollBackground[0] or e.target is @container[0])
         # Jump to new position when clicking scroller background
-        @scrollTo(e.offsetX)
+        clickTarget = e.pageX - @container.offset().left
+        @scrollTo clickTarget
+        return false

     scrollLeft: (e) =>
       e.preventDefault()


switch to `module.exports =` style in tabs.coffee

--- plugins/tabs/tabs.coffee
+++ plugins/tabs/tabs.coffee
@@ -8,43 +8,38 @@

 # click tab
 $(document).delegate '#tabs ul li:not(.add) a', 'click', ->
-  switchToTab this
+  tabs.switchToTab this
   false

 # click 'add' tab
 $(document).delegate '#tabs .add a', 'click', ->
-  addTab()
+  tabs.addTab()
   false

 # toggle
 bindKey 'toggleTabs', 'Command-Ctrl-T', (env) ->
   if $('#tabs').length
-    hideTabs()
+    tabs.hideTabs()
   else
-    showTabs()
+    tabs.showTabs()

-showTabs = ->
-  Chrome.addPane 'top', require('tabs/tabs.html')
-  $('#tabs').parents('.pane').css height: 'inherit'
-  css = $('').html require 'tabs/tabs.css'
-  $('head').append css
+module.exports = tabs =
+  showTabs: ->
+    Chrome.addPane 'top', require 'tabs/tabs.html'
+    $('#tabs').parents('.pane').css height: 'inherit'
+    css = $('').html require 'tabs/tabs.css'
+    $('head').append css

-hideTabs = ->
-  $('#tabs').parents('.pane').remove()
-  $('#tabs-style').remove()
+  hideTabs: ->
+    $('#tabs').parents('.pane').remove()
+    $('#tabs-style').remove()

-addTab = ->
-  $('#tabs ul .add').before '
untitled
'
-  $('#tabs ul .active').removeClass()
-  $('#tabs ul .add').prev().addClass 'active'
+  addTab: ->
+    $('#tabs ul .add').before '
untitled
'
+    $('#tabs ul .active').removeClass()
+    $('#tabs ul .add').prev().addClass 'active'

-switchToTab = (tab) ->
-  $('#tabs ul .active').removeClass()
-  $(tab).parents('li').addClass 'active'
-
-
-exports.show = exports.showTabs = showTabs
-exports.hideTabs = hideTabs
-exports.addTab = addTab
-exports.switchToTab = switchToTab+  switchToTab: (tab) ->
+    $('#tabs ul .active').removeClass()
+    $(tab).parents('li').addClass 'active'


Make reordering sections save them

Make reordering sections save them

--- client/src/views/section_navigation_view.coffee
+++ client/src/views/section_navigation_view.coffee
@@ -32,6 +32,7 @@
   updateOrder: (event) =>
     cids = @getOrderedCids()
     @sections.reorderByCid(cids)
+    @saveReport()

   getOrderedCids: ->
     orderedCids = []
@@ -40,5 +41,14 @@
     )
     return orderedCids

+  saveReport: ->
+    firstSection = @sections.at(0)
+    if firstSection?
+      report = firstSection.get('report')
+      Backbone.trigger 'save', 'saving'
+      report.save().done(
+        Backbone.trigger 'save', 'saved'
+      )
+
   onClose: ->
     @stopListening()


:memo: Last updates to the explanation comment

--- src/keymap-manager.coffee
+++ src/keymap-manager.coffee
@@ -435,15 +435,15 @@
     # // The binding: 'ctrl-a b c': 'my-sweet-command'
     # @queuedKeystrokes = ['ctrl-a', 'b'] // The user's keystrokes
     #
-    # When it finds partially matching bindings, it will set the keymap into a
-    # pending state via `enterPendingState`.
+    # When it finds partially matching bindings, it will set the KeymapManager
+    # into a pending state via `enterPendingState`.
     #
     # If a keystroke comes in that either matches a binding exactly, or yields
     # no partial matches, we will reset the state variables and exit pending
     # mode. If the keystroke yields no partial matches we will call
     # `terminatePendingState`
     #
-    # // Both of these will kick out of : 'ctrl-a b c': 'my-sweet-command'
+    # // Both of these will exit pending state for: 'ctrl-a b c': 'my-sweet-command'
     # @queuedKeystrokes = ['ctrl-a', 'b', 'c'] // Exact match! Just clear the state variables. Easy.
     # @queuedKeystrokes = ['ctrl-a', 'b', 'd'] // No hope of matching, terminatePendingState(). Dragons.
     #
@@ -457,7 +457,7 @@
     # 'b d': 'do-a-bd-deal'
     # 'd o g': 'wag-the-dog'
     #
-    # With these example commands, and the user's keystrokes, we should dispatch
+    # With these example bindings, and the user's keystrokes, we should dispatch
     # commands `ctrl-a-command` and `do-a-bd-deal`.
     #
     # To enable this behavior, `terminatePendingState` will _disable_ the
@@ -474,8 +474,8 @@
     # `terminatePendingState`. The 2nd call to `terminatePendingState` might
     # disable other bindings, and do another replay, which might call this
     # function again ... and on and on. It will recurse until the KeymapManager
-    # is no longer in a pending state with no partial matches on the most recent
-    # event.
+    # is no longer in a pending state with no partial matches from the most
+    # recent event.
     #
     # Godspeed.


Improve TreeCursor#_translateChildNodesAccessor

--- lib/tree_search/tree_cursor/configuration.coffee
+++ lib/tree_search/tree_cursor/configuration.coffee
@@ -50,12 +50,18 @@
   # TODO Review, Test perf. (?)
   _translateChildNodesAccessor: ->
     if @findChildNodes
-      @findFirstChildNode ?= ->
-        @get '_childNodes.firstObject'
-      @findRightSiblingNode ?= ->
-        (@get 'parent._childNodes').objectAt @get '_indexInSiblingNodes' + 1
-      @findLeftSiblingNode ?= ->
-        (@get 'parent._childNodes').objectAt @get '_indexInSiblingNodes' - 1
+      @findFirstChildNode ?= @_firstObjectInChildNodes
+      @findRightSiblingNode ?= @_rightSiblingInChildNodes
+      @findLeftSiblingNode ?= @_leftSiblingInChildNodes
+
+  _firstObjectInChildNodes: ->
+    @get '_childNodes.firstObject'
+
+  _rightSiblingInChildNodes: ->
+    (@get 'parent._childNodes').objectAt @get '_indexInSiblingNodes' + 1
+
+  _leftSiblingInChildNodes: ->
+    (@get 'parent._childNodes').objectAt @get '_indexInSiblingNodes' - 1

   # Memoized childNodes if #findChildNodes is available
   _childNodes: (->


even better preview

even better preview

--- @client/auth.coffee
+++ @client/auth.coffee
@@ -591,7 +591,7 @@

       current_user = fetch '/current_user'
-      @local.preview ?= current_user.b64_thumbnail || current_user.avatar_remote_url
+      @local.preview ?= current_user.avatar_file_name || current_user.b64_thumbnail || current_user.avatar_remote_url

       FORM
         id: 'user_avatar_form'
@@ -614,7 +614,9 @@
             style:
               width: 60
               display: if !@local.preview then 'none'
-            src: if current_user.b64_thumbnail
+            src: if current_user.avatar_file_name
+                    avatarUrl current_user.user, 'large'
+                 else if current_user.b64_thumbnail
                     current_user.b64_thumbnail
                  else if current_user.avatar_remote_url
                     current_user.avatar_remote_url


minor fix

minor fix

Former-commit-id: 85824f5d938a555ec85c0bc7f37861b5c8893ba0 [formerly 5a5ce8d59d3561477bfe696d2025d257d51dcf92] [formerly 598425f29091432595bff80643900634a3f780e8 [formerly 82e03296819105e8667ecb32a713ed61afa23e44]] [formerly e404a944fa94c01b1feb77009edc9135b8e49caa [formerly 29589cd727946c0aff90e6efd2bdce544de5ac95] [formerly 3fad5fa69370c4f6a93a1438e61695d2207fbeef [formerly 3fad5fa69370c4f6a93a1438e61695d2207fbeef [formerly 99093525eb4e366ee5d444ba8db059d635ca0cd7]]]]
Former-commit-id: 6ee6ed6202e60c7277274783887b5413f0dda785 [formerly 19b5a40feab06573eac421bd9d3a39eff47e9baa] [formerly 62333c2acc10d67dc93f48ff7b171a45531f0966 [formerly ed0e2a01d25cda9beeebcd0cd0d83968c836962b]]
Former-commit-id: 6a811b682c2488880629361b7a184b3c01602ddb [formerly f010a933f04e2601fdbbd1edc45a413fcb106688]
Former-commit-id: 0c68c2cd9f24bd8550f475933faa0a162e7da65c
Former-commit-id: c4089b1e276226c8547918f874f1c59b615e2d06

--- @client/auth.coffee
+++ @client/auth.coffee
@@ -464,7 +464,7 @@
       onChange: onChange || (event) =>
         @local[name] = current_user[name] = event.target.value
         save @local
-      onKeyPress: =>
+      onKeyPress: (event) =>
         # submit on enter
         if event.which == 13
           @submitAuth(event)
@@ -627,7 +627,7 @@
               @local.tags = @local.tags or {}
               @local.tags[question.tag] = current_user.tags[question.tag] = event.target.value
               save @local
-            onKeyPress: =>
+            onKeyPress: (event) =>
               # submit on enter
               if event.which == 13
                 @submitAuth(event)


Remove needless expects assertions.

Remove needless expects assertions.

--- tests/batman/view/formfor_rendering_test.coffee
+++ tests/batman/view/formfor_rendering_test.coffee
@@ -5,7 +5,7 @@
     @User = class User extends MockClass
       name: 'default name'

-asyncTest 'it should pull in objects for form rendering', 1, ->
+asyncTest 'it should pull in objects for form rendering', ->
   source = '''
   <form data-formfor-user="instanceOfUser">
     <input type="text" data-bind="user.name">
@@ -18,7 +18,7 @@
     equal $('input', node).val(), "default name"
     QUnit.start()

-asyncTest 'it should update objects when form rendering', 1, ->
+asyncTest 'it should update objects when form rendering', ->
   source = '''
   <form data-formfor-user="instanceOfUser">
     <input type="text" data-bind="user.name">
@@ -36,7 +36,7 @@

     QUnit.start()

-asyncTest 'it should update the context for the form if the context changes', 2, ->
+asyncTest 'it should update the context for the form if the context changes', ->
   source = '''
   <form data-formfor-user="instanceOfUser">
     <input type="text" data-bind="user.name">
@@ -51,7 +51,7 @@

     QUnit.start()

-asyncTest 'it should add the errors class to an input bound to a field on the subject', 2, ->
+asyncTest 'it should add the errors class to an input bound to a field on the subject', ->
   source = '''
   <form data-formfor-user="instanceOfUser">
     <input type="text" data-bind="user.name">
@@ -87,7 +87,7 @@

     QUnit.start()

-asyncTest 'it should add the error list HTML to the default selected node', 3, ->
+asyncTest 'it should add the error list HTML to the default selected node', ->
   source = '''
   <form data-formfor-user="instanceOfUser">

@@ -109,7 +109,7 @@

-asyncTest 'it should only show the errors list when there are errors', 2, ->
+asyncTest 'it should only show the errors list when there are errors', ->
   source = '''
   <form data-formfor-user="instanceOfUser">

@@ -128,7 +128,7 @@

     QUnit.start()

-asyncTest 'it shouldn\'t override already existing showif bindings on the errors list', 2, ->
+asyncTest 'it shouldn\'t override already existing showif bindings on the errors list', ->
   source = '''
   <form data-formfor-user="instanceOfUser">

@@ -148,7 +148,7 @@

     QUnit.start()

-asyncTest 'it should add the error list HTML to a specified selected node', 3, ->
+asyncTest 'it should add the error list HTML to a specified selected node', ->
   source = '''
   <form data-formfor-user="instanceOfUser" data-errors-list="#testy">


Resize huge uploaded images to 1600x1600 before starting image cropper. Fixed @stream.stop() is not a function error message on chrome 50. Disabled save buttons after submit.

--- app/assets/javascripts/app/controllers/_profile/avatar.coffee
+++ app/assets/javascripts/app/controllers/_profile/avatar.coffee
@@ -96,7 +96,7 @@
           replaceAvatar.text(activeAvatar.text())
           replaceAvatar.addClass('unique')
         else
-          replaceAvatar.text( '' )
+          replaceAvatar.text('')
           replaceAvatar.removeClass('unique')
     )
     avatar
@@ -122,7 +122,7 @@
         )
         processData: true
         success: (data, status, xhr) =>
-          avatarHolder = $(App.view('profile/avatar-holder')( src: src, avatar: data.avatar ) )
+          avatarHolder = $(App.view('profile/avatar-holder')(src: src, avatar: data.avatar))
           @avatarGallery.append(avatarHolder)
           @pick avatarHolder.find('.avatar')
       )
@@ -143,8 +143,7 @@

       reader.readAsDataURL(@)

-App.Config.set( 'Avatar', { prio: 1100, name: 'Avatar', parent: '#profile', target: '#profile/avatar', controller: Index }, 'NavBarProfile' )
-
+App.Config.set('Avatar', { prio: 1100, name: 'Avatar', parent: '#profile', target: '#profile/avatar', controller: Index }, 'NavBarProfile')

 class ImageCropper extends App.ControllerModal
   buttonClose: true
@@ -173,13 +172,17 @@
     if @angle == undefined
       @angle = 0

-    if @angle != 0
-      @isOrientating = true
-      image = new Image()
-      image.addEventListener 'load', @orientateImage
-      image.src = @imageSource
-    else
-      @image.attr src: @imageSource
+    show = (dataUrl) =>
+      if @angle != 0
+        @isOrientating = true
+        image = new Image()
+        image.addEventListener 'load', @orientateImage
+        image.src = dataUrl
+      else
+        @image.attr src: dataUrl
+
+    # resize if to big
+    App.ImageService.resize(@imageSource, 800, 'auto', 2, 'image/jpeg', 0.9, show)

   orientateImage: (e) =>
     image  = e.currentTarget
@@ -215,11 +218,11 @@
       minContainerHeight: 300
       preview: '.imageCropper-preview'

-  onSubmit: =>
+  onSubmit: (e) =>
+    @formDisable(e)
     @callback( @image.cropper('getCroppedCanvas').toDataURL() )
     @image.cropper('destroy')
     @close()
-

 class Camera extends App.ControllerModal
   buttonClose: true
@@ -283,7 +286,7 @@
     # in case the modal is closed before the
     # request was fullfilled
     if @hidden
-      @stream.stop()
+      @stopStream()
       return

     # cache stream so that we can later turn it off
@@ -401,11 +404,20 @@
     @video.attr height: @size

   onClose: =>
-    @stream.stop() if @stream
+    @stopStream()
     @hidden = true

-  onSubmit: =>
+  onSubmit: (e) =>
+    @formDisable(e)
+
     # send picture to the callback
     window.file = @cache.get(0).toDataURL()
     @callback @cache.get(0).toDataURL()
     @close()
+
+  stopStream: =>
+    return if !@stream
+    if @stream.getTracks
+      @stream.getTracks().forEach( (track) -> track.stop() )
+    if @stream.stop
+      @stream.stop()


Show more of results, remove apostrophes

--- commands/wolfram.coffee
+++ commands/wolfram.coffee
@@ -17,7 +17,7 @@
     message = ''

     url = 'http://api.wolframalpha.com/v2/query?' + qs.stringify(
-        input  : query
+        input  : query.replace("'", "")
         appid  : config.wolfram.apiKey
         format : ['plaintext']
     )
@@ -48,8 +48,8 @@
                 pods = root.children
                 npods = 0
                 for pod in pods
-                    if /Result|UnitConversion/.test pod.attributes.id then continue
-                    if npods > 4 or message.length > 600 then break
+                    if /UnitConversion/.test pod.attributes.id then continue
+                    if npods > 5 or message.length > 600 then break
                     subpods = pod.children.filter (node) -> node.name is 'subpod'
                     for subpod in subpods
                         if message.length > 600 then break


only default to storage0-us central fileserver name on compute machines

--- salvus/compute.coffee
+++ salvus/compute.coffee
@@ -71,15 +71,16 @@

 TIMEOUT = 60*60

-BTRFS   = if process.env.SMC_BTRFS? then process.env.SMC_BTRFS else '/projects'
+BTRFS   = process.env.SMC_BTRFS ? '/projects'
+
 BUCKET  = process.env.SMC_BUCKET
 ARCHIVE = process.env.SMC_ARCHIVE

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

 #################################################################


Simplify

Simplify

--- core/app/backbone/collections/interested_users.coffee
+++ core/app/backbone/collections/interested_users.coffee
@@ -7,10 +7,10 @@
   url: ->
     "/facts/#{@fact.id}/opinionators"

-  is_current_user_interested: ->
-    !!user_interest_state(currentSession.user().get('username'))
+  is_current_user_interested: -> !!@_current_user_interest_state()

-  user_interest_state: (username) ->
+  _current_user_interest_state: ->
+    username = currentSession.user().get('username')
     @find (vote) -> vote.get('username') == username

   setInterested: (is_interested) ->
@@ -18,7 +18,7 @@
     #TODO: why this guard - this shouldn't be possible, right?

     @fact.saveUnlessNew =>
-      current_state = @user_interest_state(currentSession.user().get('username'))
+      current_state = @_current_user_interest_state()

       if !is_interested && current_state
         current_state.destroy()
@@ -29,6 +29,6 @@
           type: 'believes'

   fetchIfUnloaded: ->
-    return if @fact.isNew() # TODO: Save a fact in the backend when submitting a comment
-
-    super
+    if !@fact.isNew()
+      super
+      # TODO: Save a fact in the backend when submitting a comment


Ensure the file is save to current provider selected

Previously, if you opened a file, then went to save-as,
switched to a new provider, and saved (without changing
the name), the file would be saved back to the previous
provider.

This is done on commit instead of every time a tab is
selected so that if you switch tabs and then *don't*
save, when you re-open the dialog your previous provider
will still be the one selected.

--- src/code/views/file-dialog-tab-view.coffee
+++ src/code/views/file-dialog-tab-view.coffee
@@ -91,6 +91,8 @@
             type: CloudMetadata.File
             provider: @props.provider
     if @state.metadata
+      # ensure the metadata provider is the currently-showing tab
+      @state.metadata.provider = @props.provider
       @props.dialog.callback @state.metadata
       @props.close()


benchmark:always emit clientengineready

benchmark:always emit clientengineready

--- benchmarks/clients/client_process.coffee
+++ benchmarks/clients/client_process.coffee
@@ -41,7 +41,7 @@

     start: ()->
         if not @optimizeConnection
-            async.each(@clientGroups,
+            async.each(@clientGroups,
                 (clientGroup, next)->
                     clientGroup.start()
                     next()
@@ -93,7 +93,7 @@
         # not a substring of another, so we can just use
         # serverResponse.substring(clientId) to see if the
         # client's events has taken effect the server DOM
-        {@browserCount, @clientCount,
+        {@browserCount, @clientCount,
         @groupName, @optimizeConnection} = options
         @clients = []
         clientsPerBrowser = @clientCount/@browserCount
@@ -128,7 +128,7 @@
             @clients[0].start()
             return @emit("started")

-        async.eachSeries(@clients,
+        async.eachSeries(@clients,
             (client, next)->
                 client.start()
                 client.once("clientEngineReady", next)
@@ -181,7 +181,6 @@
                 child.browserConfig = browserConfig
             )
             return
-

         @once('browserconfig', (browserConfig)->
             logger("#{child.id} starting")
@@ -241,9 +240,9 @@
                 @browserConfig.browserId = response.headers['x-cb-browserid']
                 @browserConfig.appId = response.headers['x-cb-appid']
                 @browserConfig.appInstanceId = response.headers['x-cb-appinstanceid']
-                # for clients that would share this browser instance
+                # for clients that share browser
                 @browserConfig.url = response.headers['x-cb-url']
-
+

             if not @browserConfig.appId? or not @browserConfig.browserId?
                 return @_fatalErrorHandler("No browserid detected.")
@@ -264,7 +263,7 @@
                 @_initialSocketIo()
             )
             logger("#{@id} opened #{@browserConfig.url}")
-
+
             @stats.add('initialPage', timeElapsed)

     _initialSocketIo : ()->
@@ -297,12 +296,13 @@
                 @once('startBenchmark', ()=>
                     @_nextEvent()
                 )
-            else
+            else
                 timers.setImmediate(()=>
                     @_nextEvent()
                 )

             #logger("#{@id} emit clientEngineReady")
+            @clientEngineReady=true
             @emit("clientEngineReady")

         @socket.on('disconnect', ()=>
@@ -339,7 +339,7 @@
                     timers.setImmediate(@_nextEvent.bind(@))
                 else
                     setTimeout(@_nextEvent.bind(@), waitDuration)
-
+

     timeOutCheck : (time)->
         if @expectStartTime? and time - @expectStartTime > @options.timeout
@@ -377,6 +377,10 @@

     _fatalErrorHandler : (@error)->
         @stats.addCounter('fatalError', "#{@id} #{error}")
+        if not @clientEngineReady
+            # the clientEngineReady will always be triggered,
+            # so the benchmark could go on even some clients fail
+            @emit("clientEngineReady")
         @stop()

     stop : () ->
@@ -462,37 +466,47 @@

 eventDescriptorReader = new benchmarkConfig.EventDescriptorsReader({fileName:opts.configFile})
 SysMon = require('../../src/server/sys_mon')
-sysMon = new SysMon()
-eventDescriptorReader.read((err, eventDescriptors)->
-    return console.log(err) if err
-
-    resultLogger = debug("cloudbrowser:benchmark:result")
-    opts.eventDescriptors = eventDescriptors
-    clientProcess = new ClientProcess(opts)
-    clientProcess.start()
-    clientProcess.once("started",(err)->
-        return console.log("client process start failed #{err}") if err?
-
+sysMon = new SysMon({
+    interval : 5000
+    })
+clientProcess = null
+benchmarkFinished = false
+resultLogger = debug("cloudbrowser:benchmark:result")
+
+async.waterfall([
+    (next)->
+        eventDescriptorReader.read(next)
+    (eventDescriptors, next)->
+        opts.eventDescriptors = eventDescriptors
+        clientProcess = new ClientProcess(opts)
+        clientProcess.start()
+        clientProcess.once("started", next)
+    (next)->
         clientProcess.startBenchmark()
-
         intervalObj = setInterval(()->
             resultLogger("Elapsed #{(new Date()).getTime() - clientProcess.stats.startTime}ms")
             clientProcess.timeOutCheck()
             resultLogger JSON.stringify(clientProcess.stats)
             sysMon.logStats()
             if clientProcess.isStopped()
+                clearInterval(intervalObj)
+                sysMon.stop()
+                clientProcess.stats.accumulate()
+                resultLogger JSON.stringify(clientProcess.stats)
                 resultLogger "stopped"
-                clearInterval(intervalObj)
-
-                clientProcess.stats.accumulate()
-
-                resultLogger JSON.stringify(clientProcess.stats)
-
+                benchmarkFinished = true
                 process.exit(1)
         , 3000
         )
-
+        next()
+    ], (err)->
+        console.log("clientProcess #{opts.processId} error #{err}") if err?
 )

-
+process.on('SIGTERM',()->
+    if clientProcess and not benchmarkFinished
+        clientProcess.stats.accumulate()
+        resultLogger JSON.stringify(clientProcess.stats)
+        resultLogger "terminated"
+        process.exit(1)
 )


also dont show isntructions on teacher review, organize code  alittle

--- src/components/question/answers-table.cjsx
+++ src/components/question/answers-table.cjsx
@@ -55,6 +55,17 @@
       changeEvent.preventDefault()
       @props.onChangeAttempt?(answer)

+  hasCorrectAnswer: ->
+    {correct_answer_id} = @props
+
+    !! correct_answer_id
+
+  shouldInstructionsShow: ->
+    {type, model} = @props
+    hasCorrectAnswer = @hasCorrectAnswer()
+
+    model.formats.length > 1 and not (hasCorrectAnswer or type in ['teacher-preview', 'teacher-review'])
+
   render: ->
     {
       model, type, answered_count, choicesEnabled, correct_answer_id,
@@ -66,7 +77,7 @@

     chosenAnswer = [answer_id, @state.answer_id]
     checkedAnswerIndex = null
-    hasCorrectAnswer = !! correct_answer_id
+    hasCorrectAnswer = @hasCorrectAnswer()

     questionAnswerProps =
       qid: id or "auto-#{idCounter++}"
@@ -93,7 +104,7 @@

     instructions = <Instructions
       project={project}
-    /> if model.formats.length > 1 and not (hasCorrectAnswer or type is 'teacher-preview')
+    /> if @shouldInstructionsShow()

     <div className='answers-table'>
       {instructions}


adding hub_servers to stats query

adding hub_servers to stats query

--- salvus/rethink.coffee
+++ salvus/rethink.coffee
@@ -62,6 +62,7 @@
             last_day_projects : true
             last_week_projects : true
             last_month_projects : true
+            hub_servers : true
         indexes:
             timestamp : []
         user_query:
@@ -79,6 +80,7 @@
                     last_day_projects : true
                     last_week_projects : true
                     last_month_projects : true
+                    hub_servers : true
     file_use:
         primary_key: 'id'
         fields:


Removed erroneous info message

Removed erroneous info message

--- src/wrathnet/net/socket.coffee
+++ src/wrathnet/net/socket.coffee
@@ -50,8 +50,6 @@
       @port = port
       @uri = 'ws://' + @host + ':' + @port

-      console.info 'connecting to auth-server @', @host, ':', @port
-
       @socket = new WebSocket(@uri, 'binary')
       @socket.binaryType = 'arraybuffer'


client: change header for modal when no machine is found

--- client/app/lib/providers/environmentsmachinestatemodal.coffee
+++ client/app/lib/providers/environmentsmachinestatemodal.coffee
@@ -357,7 +357,7 @@

     if @state is 'NotFound'
       @createStateLabel "
-
No machine found!

+
You don't have any machines!

           This can happen if you have deleted all your VMs or if your VM was automatically deleted due to inactivity. Learn more about inactive VM cleanup.


MachineStateModal: Destroy modal when add new machine button clicked.

--- client/app/lib/providers/environmentsmachinestatemodal.coffee
+++ client/app/lib/providers/environmentsmachinestatemodal.coffee
@@ -116,7 +116,7 @@
         if /NetworkOut/i.test error

           limitReachedNotice = "
-
You've reached your outbound network
+
You've reached your outbound network
             usage limit for this week.

           "

@@ -131,7 +131,7 @@
           else
             @customErrorMessage = "
               #{limitReachedNotice}
-              Please contact support
+              Please contact support
               for further assistance.
             "

@@ -662,7 +662,10 @@
     findNodes = new FindManagedNodesModal { container }, @machine

-  requestNewMachine: -> new EnvironmentsModal
+  requestNewMachine: ->
+
+    new EnvironmentsModal
+    @destroy()

   turnOnMachine: ->


UI Copy: "Still waiting... This VM is booting slowly."

If you guys don't like this, let me know and I will think of something else

--- client/finder/filetree/itemsubviews/vmtogglebuttonview.coffee
+++ client/finder/filetree/itemsubviews/vmtogglebuttonview.coffee
@@ -23,7 +23,7 @@
         type    : "mini"
         cssClass: "error"
         duration: 5000
-        title   : "I cannot turn this machine on, please give it a few seconds."
+        title   : "Still waiting... This VM is booting slowly."

       @toggle.setDefaultValue no
       # KD.utils.notifyAndEmailVMTurnOnFailureToSysAdmin vm, err.message


added log items to main.coffee

added log items to main.coffee

--- bokehjs/src/coffee/main.coffee
+++ bokehjs/src/coffee/main.coffee
@@ -45,6 +45,7 @@

   # mappers
   Bokeh.LinearMapper      = require("mapper/1d/linear_mapper")
+  Bokeh.LogMapper         = require("mapper/1d/log_mapper")
   Bokeh.CategoricalMapper = require("mapper/1d/categorical_mapper")
   Bokeh.GridMapper        = require("mapper/2d/grid_mapper")
   Bokeh.LinearColorMapper = require("mapper/color/linear_color_mapper")
@@ -96,6 +97,7 @@
   Bokeh.DatetimeAxis    = require("renderer/guide/datetime_axis")
   Bokeh.Grid            = require("renderer/guide/grid")
   Bokeh.LinearAxis      = require("renderer/guide/linear_axis")
+  Bokeh.LogAxis         = require("renderer/guide/log_axis")

   # overlays
   Bokeh.BoxSelection = require("renderer/overlay/box_selection")
@@ -116,6 +118,8 @@
   Bokeh.AdaptiveTicker           = require("ticking/adaptive_ticker")
   Bokeh.BasicTicker              = require("ticking/basic_ticker")
   Bokeh.BasicTickFormatter       = require("ticking/basic_tick_formatter")
+  Bokeh.LogTicker                = require("ticking/log_ticker")
+  Bokeh.LogTickFormatter         = require("ticking/log_tick_formatter")
   Bokeh.CategoricalTicker        = require("ticking/categorical_ticker")
   Bokeh.CategoricalTickFormatter = require("ticking/categorical_tick_formatter")
   Bokeh.CompositeTicker          = require("ticking/composite_ticker")


RPM: Do not rewrite config on update

RPM: Do not rewrite config on update

--- gulp/rpm.coffee
+++ gulp/rpm.coffee
@@ -11,7 +11,7 @@
   type:        'systemd'
   name:        options.name
   description: options.description
-  exec:        '/usr/bin/teeworlds-fluent-forwarder'
+  exec:        "/usr/bin/#{options.name}"
   user:        'nobody'
   group:       'nobody'
 }
@@ -44,6 +44,10 @@
   gulp.src './dist/teeworlds-fluent-forwarder.env'
     .pipe brass.util.rename(options.service.name)
     .pipe gulp.dest(path.join(rpm.buildRoot, '/etc/sysconfig'))
+    .pipe brass.util.stream (file, done) ->
+      file.config = true
+      file.noreplace = true
+      done null, file
     .pipe rpm.files()

 gulp.task 'rpm:binaries', ['rpm:files'], npm.binariesTask(pkg, rpm)


enable src maps

enable src maps

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -21,7 +21,7 @@
 #    bail: true
     watch: watch
     cache: true
-    # devtool: "source-map"
+    devtool: "source-map"
     output:
       filename: "#{name}.js"
       sourceMapFilename: "[file].map"


fix watch

fix watch

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -52,7 +52,7 @@
 gulp.task 'server', ['server:coffee']

 gulp.task 'watch', () ->
-  gulp.watch ['./client/stylus/**/*.stylus'], ['client:stylus']
+  gulp.watch ['./client/stylus/**/*.styl'], ['client:stylus']
   gulp.watch ['./client/coffee/**/*.coffee'], ['client:coffee']
   gulp.watch ['./server/**/*.coffee'], ['server:coffee']
   gulp.watch ['./client/index.html'], ['client:resource']


docs(comments): improve comments in gulpfile.coffee

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -10,6 +10,23 @@
 	config    = require("#{rbDir}/config") rbDir, options
 	tasks     = require("#{config.req.init}/tasks") gulp, config
 	promise   = require("#{config.req.init}/rapid") gulp, config
-	(env = 'default') -> # kick off rapid-build task
+
+	# sequence:
+	# 1. init rapid
+	# 2. execute rapid to kick off the build
+	# 3. return promise when build is complete
+	#
+	# note: function not executed if app calls build task from gulp
+	# example - gulpfile.js:
+	#   gulp.task 'default', ['rapid-build:dev']
+	#
+	# example - build.js:
+	#   terminal: node build dev
+	#   build.js:
+	#     build = process.argv[2]
+	#     rapid = require('rapid-build') opts
+	#     rapid(build).done -> console.log 'build finished'
+	# =========================================================
+	(env = 'default') ->
 		gulp.start config.rb.tasks[env]
 		promise


Add minimal delay

Add minimal delay

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -89,6 +89,7 @@
     proxy: "http://127.0.0.1:#{config.server.port}"
     files: ['app/public/assets/**/*.*']
     port: config.serverDev.port
+    reloadDelay: 1000
   return

 gulp.task 'build', ['css', 'js']


Started working on setting up Browserify.

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -12,6 +12,8 @@
 exec = require('child_process').exec
 nib = require 'nib'
 stylus = require 'gulp-stylus'
+browserify = require 'browserify'
+coffeeify = require 'coffeeify'
 pkg = require './package.json'

 GLOBAL.buildFileName = 'championify'
@@ -49,6 +51,14 @@
   .pipe gulp.dest('./dev/css')

+gulp.task 'browserify', (cb) ->
+    browserify({
+      transform: [coffeeify]
+      entries: ['./functions/main.coffee']
+    })
+    .bundle()
+
+
 gulp.task 'run-watch', (cb) ->
   gulp.watch './stylesheets/*.styl', ['stylus']


fix(phantomjs): fix windows binary path

fix(phantomjs): fix windows binary path

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -46,6 +46,8 @@
 typeScript            = require 'gulp-typescript'
 uglify                = require 'gulp-uglify'

+appUrl = "http://localhost:#{DEV_PORT}"
+
 bowerComponents = do ->
 	components = {}

@@ -67,7 +69,7 @@

 	components

-appUrl = "http://localhost:#{DEV_PORT}"
+isWindows = /^win/.test(process.platform)

 gulp.task 'bower', ->
 	deferred = q.defer()
@@ -151,7 +153,8 @@
 gulp.task 'docs', ['yuidoc']

 gulp.task 'e2e', ->
-	e2eConfigFile = path.join './', TEMP_DIRECTORY, 'e2e-config.coffee'
+	e2eConfigFile       = path.join './', TEMP_DIRECTORY, 'e2e-config.coffee'
+	phantomjsBinaryPath = './node_modules/.bin/phantomjs' + if isWindows then '.cmd'

 	# create temporary e2e-config file to avoid an additional config file
 	# currently gulp-protractor requires one the existence of an e2e-config file
@@ -168,9 +171,10 @@
 	options =
 		configFile: e2eConfigFile
 		args: [
+
 			'--baseUrl', appUrl,
 			'--browser', 'phantomjs'
-			'--capabilities.phantomjs.binary.path', './node_modules/phantomjs/bin/phantomjs'
+			'--capabilities.phantomjs.binary.path', phantomjsBinaryPath
 		]

 	gulp
@@ -357,7 +361,6 @@

 gulp.task 'test', ['build'], ->
 	# don't allow karma to block gulp
-	isWindows = process.platform is 'win32'
 	command = if isWindows then '.\\node_modules\\.bin\\gulp.cmd' else 'gulp'
 	spawn = childProcess.spawn command, ['karma'], {stdio: 'inherit'}


Dont use sdk version for release

Dont use sdk version for release

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -20,7 +20,7 @@
     nw = new NwBuilder(
       files: './src/**'
       platforms: [platform]
-      version: '0.14.5-sdk'
+      version: '0.14.5'
       winIco: if process.argv.indexOf('--noicon') > 0 then undefined else './assets-windows/icon.ico'
       macIcns: './assets-osx/icon.icns'
       macZip: true


Serve src files as root

Serve src files as root

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -23,7 +23,7 @@

   # Start a webpack-dev-server
   new WebpackDevServer webpack(conf),
-    publicPath: '/' + conf.output.publicPath
+    contentBase: conf.contentBase
     stats:
       colors: true
   .listen 8080, 'localhost', (err) ->


Fix dir

Fix dir

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -80,7 +80,7 @@
       encoding: null
     yield fs.writeFileAsync path.join(path.homedir(), "electron-v#{ELECTRON_VERSION}-#{PLATFORM}.zip"), body
   log "Extract Electron #{ELECTRON_VERSION}"
-  zip = new AdmZip path.join(path.tempdir(), "electron-v#{ELECTRON_VERSION}-#{PLATFORM}.zip")
+  zip = new AdmZip path.join(path.homedir(), "electron-v#{ELECTRON_VERSION}-#{PLATFORM}.zip")
   zip.extractAllTo dir, true

 gulp.task 'copy-files', ['download-electron'], ->
@@ -105,7 +105,7 @@
     linux: 'http://7xj6zx.com1.z0.glb.clouddn.com/poi/PepperFlash/linux.zip'
     darwin: 'http://7xj6zx.com1.z0.glb.clouddn.com/poi/PepperFlash/darwin.zip'
   url = plugins[PLATFORM]
-  dir = path.join(path.homedir(), "poi-v#{POI_VERSION}-#{PLATFORM}-#{SYSTEM_BIT[PLATFORM]}", 'PepperFlash')
+  dir = path.join(path.tempdir(), "poi-v#{POI_VERSION}-#{PLATFORM}-#{SYSTEM_BIT[PLATFORM]}", 'PepperFlash')
   fs.ensureDirSync dir
   try
     yield fs.accessAsync path.join(path.homedir(), "flashplayer-#{PLATFORM}.zip"), fs.R_OK
@@ -114,9 +114,9 @@
     [response, body] = yield requestAsync
       url: url
       encoding: null
-    yield fs.writeFileAsync path.join(path.tempdir(), "flashplayer-#{PLATFORM}.zip"), body
+    yield fs.writeFileAsync path.join(path.homedir(), "flashplayer-#{PLATFORM}.zip"), body
   log "Extract flash plugin"
-  zip = new AdmZip path.join(path.tempdir(), "flashplayer-#{PLATFORM}.zip")
+  zip = new AdmZip path.join(path.homedir(), "flashplayer-#{PLATFORM}.zip")
   zip.extractAllTo dir, true

 gulp.task 'build', ['get-flash-player'], async ->


Added TRAVIS_BUILD as NODE_ENV

Added TRAVIS_BUILD as NODE_ENV

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -24,7 +24,14 @@
       .pipe gulp.dest('./public/assets/dist/')

 gulp.task 'coffee:compile', ->
-  environment = process.env.NODE_ENV ? 'development'
+  if process.env.TRAVIS_BUILD
+    switch process.env.TRAVIS_BUILD
+      when 'master' then environment = 'production'
+      when 'staging' then environment = 'staging'
+      else environment = 'production'
+  else
+    environment = process.env.NODE_ENV ? 'development'
+
   configFile = "./config/#{environment}.coffee"

   files = ['./app/**/*.coffee', configFile]


Removed emitOnGlob: false from the less and coffeescript watchers, so they will build our included files

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -84,10 +84,10 @@
   watch(glob: "server/assets/**/*", emitOnGlob: false, ["reload"])

 gulp.task "watchLess", ()->
-  watch(glob: "assets/less/**", emitOnGlob: false, ["buildLess"])
+  watch(glob: "assets/less/**", ["buildLess"])

 gulp.task "watchCoffee", ()->
-  watch(glob: "assets/coffee/**", emitOnGlob: false, ["buildCoffee"])
+  watch(glob: "assets/coffee/**", ["buildCoffee"])


Amend bdacce15a911ce247913ccd5e4eb4a1f51365a4c

Feedback from Anthony: `@start` is always a `DocumentStartToken`.

--- src/model.coffee
+++ src/model.coffee
@@ -696,11 +696,8 @@
   # ## getFromTextLocation ##
   # Given a TextLocation, find the token at that row, column, and length.
   getFromTextLocation: (location) ->
-    head = @start
+    head = @start.next # Move past the DocumentStartToken
     best = head
-
-    if head instanceof DocumentStartToken
-      head = head.next

     row = 0


Handle the special case when the final line of the range is empty

--- src/match-iterator.coffee
+++ src/match-iterator.coffee
@@ -52,7 +52,7 @@

     line = line.slice(0, @range.end.column - lineOffset)
     while match = @regex.exec(line)
-      break if match.index is @range.end.column
+      break if line.length isnt 0 and match.index is @range.end.column
       argument = new SingleLineSearchCallbackArgument(@buffer, row, match, lineOffset)
       callback(argument)
       return if argument.stopped or not global


remove forced public exposure

remove forced public exposure

--- library/nucleus/kernel.coffee
+++ library/nucleus/kernel.coffee
@@ -244,7 +244,6 @@
         opts = nconf.get "assets:opts"
         assert _.isObject(opts), "no assets options"
         assert _.isArray(dirs), "no assets directories"
-        (dirs ?= []).push "#{__dirname}/../../public"
         for directory in dirs then do (directory) =>
             cwd = process.cwd().toString()
             solved = paths.relative cwd, directory


Edited gameState.coffee via GitHub

Edited gameState.coffee via GitHub

--- gameState.coffee
+++ gameState.coffee
@@ -316,11 +316,11 @@
     @current = @players[0]
     @supply = this.makeSupply(tableau)
     @prizes = [c["Bag of Gold"], c.Diadem, c.Followers, c.Princess, c["Trusty Steed"]]
+    @tradeRouteMat = []

     @bridges = 0
     @quarries = 0
     @copperValue = 1
-    @tradeRouteValue = 0
     @phase = 'start'
     return this

@@ -424,6 +424,13 @@
     if @supply['Colony']?
       minimum = Math.min(minimum, @supply['Colony'])
     minimum
+
+  # 'tradeRouteValue()' returns the current value of the Trade Route by
+  # counting the number of cards in the @tradeRouteMat pool.
+  tradeRouteValue: () ->
+    value = @tradeRouteMat.length
+    this.log("Trade Route value = $#{value}.")
+    value

   #### Playing a turn
   #
@@ -641,6 +648,8 @@
         @prizes.remove(card)
       else
         @supply[card] -= 1
+      if @supply["Trade Route"]? and card.isVictory and not in @tradeRouteMat
+        @tradeRouteMat.push(card)
     else
       this.log("There is no #{card} to gain.")
     # TODO: handle gain reactions
@@ -787,6 +796,7 @@
     newState.current = newPlayers[0]
     newState.nPlayers = @nPlayers
     newState.bridges = @bridges
+    newState.tradeRouteMat = @tradeRouteMat
     newState.quarries = @quarries
     newState.copperValue = @copperValue
     newState.phase = @phase


Mathjax loading refactored so config works

This fixes LaTeX math.

--- scripts/config.coffee
+++ scripts/config.coffee
@@ -12,6 +12,8 @@
     session: 'gh-book/session'
     jsSHA: "#{BOWER}/jsSHA/src/sha_dev" # Calculate the sha1 hash for resources

+
+    mathjax: 'http://cdn.mathjax.org/mathjax/2.0-latest/MathJax.js?config=TeX-MML-AM_HTMLorMML-full&delayStartupUntil=configured'

     # ## Template and Style paths
     templates: '../templates'
@@ -109,18 +111,20 @@
       exports: 'Select2'

     aloha:
-      deps: ['jquery', 'bootstrapModal', 'bootstrapPopover', 'cs!configs/aloha', 'cs!configs/mathjax']
+      deps: ['jquery', 'mathjax', 'cs!configs/aloha', 'bootstrapModal', 'bootstrapPopover']
       exports: 'Aloha'
-      init: ($, alohaConfig, mathjaxConfig) ->
-        script = document.createElement("script")
-        script.src = "http://cdn.mathjax.org/mathjax/2.0-latest/MathJax.js?config=TeX-MML-AM_HTMLorMML-full&delayStartupUntil=configured"
-        script.text = 'MathJax.Hub.Config(' + JSON.stringify(mathjaxConfig) + ');' + 'MathJax.Hub.Startup.onload();'
+      init: () ->
+        jQuery.browser.version = 10000 # Hack to fix aloha-editor's version checking
+        return Aloha

-        document.getElementsByTagName("head")[0].appendChild(script);
+    mathjax:
+      deps: ['cs!configs/mathjax']
+      exports: 'MathJax'
+      init: (mathjaxConfig) ->
+        MathJax.Hub.Config(mathjaxConfig)
+        MathJax.Hub.Startup.onload()
+        return MathJax

-        jQuery.browser.version = 10000 # Hack to fix aloha-editor's version checking
-
-        return Aloha

   # Handlebars Requirejs Plugin Configuration
   # This configures `requirejs` plugins (used when loading templates `'hbs!...'`).


Add modal on setting

Add modal on setting

--- views/components/settings/parts/poi-config.cjsx
+++ views/components/settings/parts/poi-config.cjsx
@@ -29,6 +29,7 @@
     config.set 'poi.tabarea.double', !enabled
     @setState
       enableDoubleTabbed: !enabled
+    toggleModal '布局设置', '设置成功，请重新打开软件使得布局生效。'
   handleSetLayout: (layout) ->
     return if @state.layout == layout
     config.set 'poi.layout', layout


fixed a input param for `onLoad` event

fixed a input param for `onLoad` event

--- src/ngPreviewCover.coffee
+++ src/ngPreviewCover.coffee
@@ -170,7 +170,7 @@
             @cover = ""
             @hideMenuBox()
             buildEmit( "load" )( base64 )
-            scope.onLoad base64
+            scope.onLoad base64:base64
             $timeout =>
               @disableScroll()
               @showMenuBox()


Remove the regex matching.

Remove the regex matching.

--- scripts/quotes.coffee
+++ scripts/quotes.coffee
@@ -4,7 +4,7 @@
 # Commands:
 #   hubot quote add <quote>                - Adds the specified quote. Regulars and up only.
 #   hubot quote delete|remove <quote ID>   - Remove the specified quote. Mod and up only.
-#   hubot quote <ID>|<Regex match>         - Return a specific quote.
+#   hubot quote <ID>                       - Return a specific quote.
 #   hubot quote delete|remove all          - Removes all quotes. Admin only.
 #
 # Author:
@@ -18,7 +18,7 @@
     default_quote_database = {
         "next_id": 1,
         "quotes": [
-            {"id": 0, "quote": "Th1s1sy0urf1rstqu0te1nth3d4t4b4s3"}
+            {"id": 0, "quote": "First quote"}
         ],
         "rmquote": []
     }
@@ -65,11 +65,11 @@
                 msg.send "You need to specify a quote ID to remove."

     # Returns stored quotes.
-    robot.respond /quote\s?(?: (\d+)|\s(.*))?$/i, (msg) ->
+    robot.respond /quote\s?(?: (\d+))?$/i, (msg) ->
         quote_database = robot.brain.data.quote_database

         # Returns a random quote.
-        if not msg.match[1] and not msg.match[2]
+        if not msg.match[1]
             random_quote_index = Math.floor(Math.random() * quote_database.quotes.length)
             random_quote = quote_database.quotes[random_quote_index]
             msg.send "#{random_quote.quote} [ID: #{random_quote.id}]"
@@ -83,23 +83,6 @@
                     return
             msg.send "The quote ID seems invalid [ID: #{msg.match[1]}]."

-        # Returns quote from regex match.
-        else if msg.match[2]
-            quote_found_count = 0
-            for quote_index in robot.brain.data.quote_database.quotes
-                if quote_index.quote.match new RegExp(msg.match[2])
-                    quote_found_count++
-                if quote_found_count <= maximum_quotes_output
-                    msg.send "#{quote_index.quote} [ID: #{quote_index.id}]"
-
-            if quote_found_count > maximum_quotes_output
-                excess_quote_count = quote_found_count - maximum_quotes_output
-                msg.send "There were [#{excess_quote_count}] more quotes found.  To retrieve all of these run again with quoteall.  For example: 'quoteall (F|f)oobar'."
-                return
-
-            else if quote_found_count < 1
-                msg.send "There were no matching quotes found [Pattern: #{msg.match[2]}]."
-
     robot.respond /quote (delete|remove) all$/i, (msg) ->
         if robot.auth.hasRole(msg.envelope.user, ['admin'])
             robot.brain.data.quote_database = default_quote_database


test(engine): run mocha in parallel

test(engine): run mocha in parallel

--- packages/engine/test/actions/execute/wait.coffee
+++ packages/engine/test/actions/execute/wait.coffee
@@ -51,7 +51,6 @@
   describe 'log', ->

     they 'attemps', ({ssh}) ->
-      logs = []
       nikita
         ssh: ssh
         tmpdir: true
@@ -67,7 +66,7 @@
           log: ({log}) ->
             logs.push log if /Attempt #\d/.test log.message
         @call ->
-          logs.length.should.be.within 2, 4
+          logs.length.should.be.within 2, 8

     they 'honors *_log as true', ({ssh}) ->
       nikita


sachin with relatives

sachin with relatives

--- scripts/sachin.coffee
+++ scripts/sachin.coffee
@@ -21,7 +21,8 @@
 cuteLinks = [
     "https://raw.githubusercontent.com/sagasu/offshore-team/master/img/0.jpeg",
     "https://raw.githubusercontent.com/sagasu/offshore-team/master/img/1.jpg",
-    "https://raw.githubusercontent.com/sagasu/offshore-team/master/img/2.png"
+    "https://raw.githubusercontent.com/sagasu/offshore-team/master/img/2.png",
+    "https://raw.githubusercontent.com/sagasu/offshore-team/master/img/sachinWithRelatives.jpg"
 ]

 justhead = [
@@ -36,6 +37,10 @@
     Math.floor(Math.random() * cuteLinks.length)
     msg.send cuteLinks[Math.floor(Math.random() * cuteLinks.length)] for element in elements

+  robot.respond /what is Sachin doing/i, (msg)->
+    msg.send "https://raw.githubusercontent.com/sagasu/offshore-team/master/img/sachinWithRelatives.jpg"
+    msg.send "Meeting his wife relatives in India."
+
   robot.respond /sachin super duper bomb( (\d+))?/i, (msg)->
     count = msg.match[2] || 5
     elements = [1..count]


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/point.coffee
+++ src/point.coffee
@@ -631,7 +631,7 @@
   @returns {Point}
   ###
   Point.random = ->
-    Point
+    Point.fromAngle
   ###*
   @name ZERO
   @fieldOf Point


Add language-slim Atom package # # 72-character wrapped longer description. This should answer: # # * Why was this change necessary? # * How does it address the problem? # * Are there any side effects? # # Include a link to the ticket, if any.

--- atom/packages.cson
+++ atom/packages.cson
@@ -19,6 +19,7 @@
   "language-haml"
   "language-pug"
   "language-rspec"
+  "language-slim"
   "linter"
   "linter-ui-default"
   "linter-write-good"


Add more C++ operator overload snippets

Add more C++ operator overload snippets

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -104,9 +104,34 @@
     'prefix': 'op='
     'body': "${1:Class}& operator=(const ${1:Class}& other)$2"

-  'friend operator':
-    'prefix': 'fop'
+  'operator (friend)':
+    'prefix': 'opfriend'
     'body': "${1:friend }${2:Class} operator${3:+}(const ${4:Left}& left, const ${5:Right}& right)$6"
+
+  'operator (<<)':
+    'prefix': 'op<<'
+    'body': "${1:friend }ostream& operator<<(ostream& out, const ${2:Class}& other)$3"
+
+  'operator ([])':
+    'prefix': 'op[]'
+    'body': "${1:Class}& operator[](int) const$5"
+
+  'operator (++x)':
+    'prefix': 'op++x'
+    'body': "${1:Class}& operator${2:++}()$3"
+
+  'operator (x++)':
+    'prefix': 'opx++'
+    'body': "${1:Class} operator${2:++}(int)$3"
+
+  'operator (+)':
+    'prefix': 'op+'
+    'body': "${1:Class} operator${2:+}() const$3"
+
+  'operator (+=/-=)':
+    'prefix': 'op+='
+    'body': "${1:Class} operator${2:+}=() const$3"
+

 # JavaScript


Convert tabs to spaces in Atom snippets file

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -2,16 +2,16 @@

 '.source.js':

-	# Jasmine
+  # Jasmine

-	'it':
-		'prefix': 'it'
-		'body': "it('$1', function ($2) {\n\t$3\n});$4"
+  'it':
+    'prefix': 'it'
+    'body': "it('$1', function ($2) {\n\t$3\n});$4"

-	'describe':
-		'prefix': 'desc'
-		'body': "describe('$1', function ($2) {\n\t$3\n});$4"
+  'describe':
+    'prefix': 'desc'
+    'body': "describe('$1', function ($2) {\n\t$3\n});$4"

-	'expect':
-		'prefix': 'expect'
-		'body': "expect($1).${2:toBe}($3);$4"
+  'expect':
+    'prefix': 'expect'
+    'body': "expect($1).${2:toBe}($3);$4"


Add TypeScript React snippet

Add TypeScript React snippet

--- atom.symlink/snippets.cson
+++ atom.symlink/snippets.cson
@@ -4,10 +4,21 @@
     'body': '''
       import React from 'react'

-      class $1 extends React.Component {
-        render() {
-          $2
-        }
+      const $1 = () => {
+        $2
+      }
+
+      export default $1
+    '''
+
+'.source.tsx':
+  'React Component':
+    'prefix': 'rcom'
+    'body': '''
+      import React from 'react'
+
+      const $1 = () => {
+        $2
       }

       export default $1


fix order

fix order

--- build/html-builder.coffee
+++ build/html-builder.coffee
@@ -174,7 +174,7 @@
         for name in Object.keys(indexes)
             indexes[name].sort (a, b)->
                 if a.sort is b.sort
-                    if a.name < b.name then -1 else +1
+                    if a.name.toLowerCase() < b.name.toLowerCase() then -1 else +1
                 else a.sort - b.sort
             indexes[name] = indexes[name].map (x)->
                 name:formatName(x.name), url:x.url, dev:x.dev


Trigger event when enter is pressed but current value is unrecognised

--- autocomplete-client.coffee
+++ autocomplete-client.coffee
@@ -296,9 +296,11 @@
   # Handle selection of autocomplete result item
   select: () ->
     node = @tmplInst.find(".-autocomplete-item.selected")
-    return false unless node?
-
-    if node.classList.contains("footer")
+
+    if not node?
+      @triggerNoMatchAction()
+      return false
+    else if node.classList.contains("footer")
       @triggerFooterAction()
       return true
     else
@@ -337,7 +339,14 @@
     @setText("")

   triggerNoMatchAction: (e) ->
-    @$element.trigger(@rules[@matched].noMatchAction)
+    action = @rules[@matched].noMatchAction
+    inputText = @getText()
+
+    @hideList()
+    @setText("")
+
+    if action
+      @$element.trigger(action, inputText)

   # Replace the appropriate region
   replace: (replacement) ->


Update yayyyy.coffee

Update yayyyy.coffee

--- scripts/yayyyy.coffee
+++ scripts/yayyyy.coffee
@@ -3,10 +3,9 @@
 #

 yays = [
-  "http://mlkshk.com/r/KSB3.gif",
   "yayyyyyyy!\nhttp://i.imgur.com/zyJuF.gif"
 ]

 module.exports = (robot) ->
   robot.hear /\byay+\b/i, (msg) ->
-    msg.send msg.random yays+    msg.send msg.random yays


Fixed cf colors

Fixed cf colors

--- server/calculations/calculateCfRating.coffee
+++ server/calculations/calculateCfRating.coffee
@@ -3,7 +3,7 @@
 import logger from '../log'

 colors = [[0, "gray"], [1200, "green"], [1400, "#03A89E"], [1600, "blue"],
-         [1900, "#a0a"], [2100, "#bb0"], [2300, "#FF8C00"], [2400, "red"]];
+         [1900, "#a0a"], [2100, "#FF8C00"], [2400, "red"]];

 getRating = (user) ->
     href = "http://codeforces.com/api/user.info?handles=" + user.cf.login


Remove two unused LevelSessionHandler routes

--- server/handlers/level_session_handler.coffee
+++ server/handlers/level_session_handler.coffee
@@ -9,9 +9,7 @@
   modelClass: LevelSession

   getByRelationship: (req, res, args...) ->
-    return @getActiveSessions req, res if args.length is 2 and args[1] is 'active'
     return @getRecentSessions req, res if args.length is 2 and args[1] is 'recent'
-    return @getCodeLanguageCounts req, res if args[1] is 'code_language_counts'
     super(arguments...)

   formatEntity: (req, document) ->
@@ -27,16 +25,6 @@
       document.interpret = plan
       document.code = {'hero-placeholder': {plan: ''}, 'hero-placeholder-1': {plan: ''}}
     return document
-
-  getActiveSessions: (req, res) ->
-    return @sendForbiddenError(res) unless req.user?.isAdmin()
-    start = new Date()
-    start = new Date(start.getTime() - TIMEOUT)
-    query = @modelClass.find({'changed': {$gt: start}})
-    query.exec (err, documents) =>
-      return @sendDatabaseError(res, err) if err
-      documents = (@formatEntity(req, doc) for doc in documents)
-      @sendSuccess(res, documents)

   getRecentSessions: (req, res) ->
     return @sendForbiddenError(res) unless req.user?.isAdmin()
@@ -63,22 +51,5 @@
     return true if get and not document.get('submittedCode')  # Allow leaderboard access to non-multiplayer sessions
     super(arguments...)

-  getCodeLanguageCounts: (req, res) ->
-    if @codeLanguageCache and (new Date()) - @codeLanguageCountCachedSince > 86400 * 1000  # Dumb cache expiration
-      @codeLanguageCountCache = null
-      @codeLanguageCountCacheSince = null
-    if @codeLanguageCountCache
-      return @sendSuccess res, @codeLanguageCountCache
-    query = LevelSession.aggregate [
-      #{$match: {codeLanguage: {$exists: true}}}  # actually slows it down
-      {$group: {_id: "$codeLanguage", sessions: {$sum: 1}}}
-      {$sort: {sessions: -1}}
-    ]
-    query.exec (err, data) =>
-      if err? then return @sendDatabaseError res, err
-      @codeLanguageCountCache = data
-      @codeLanguageCountCachedSince = new Date()
-      @sendSuccess res, data
-

 module.exports = new LevelSessionHandler()


add tests for api/applications/install

add tests for api/applications/install

--- test/applications_tests.coffee
+++ test/applications_tests.coffee
@@ -1,4 +1,4 @@
-should = require('should')
+should = require('chai').Should()
 client = require('../common/test/client')
 server = require('../server')

@@ -102,6 +102,33 @@
             bodyTest.rows.length.should.equal 1
             bodyTest.rows[0].name.should.equal "Noty plus"

+    describe "POST /api/applications/install Install a new application", ->
+        it "When I send a request to install an application", (done) ->
+            data =
+                name: "My App",
+                description: "description",
+                git: "git@github.com:mycozycloud/my-app.git"
+            client.post "api/applications/install", \
+                        data, (error, response, body) =>
+                @response = response
+                @body = body
+                done()
+
+        it "Then it sends me back my app with an id and a state", ->
+            @response.statusCode.should.equal 201
+            should.exist @body.id
+            should.exist @body.state
+
+        it "When I send a request to retrieve all applications", (done) ->
+            client.get "api/applications", (error, response, body) =>
+                @body = JSON.parse body
+                done()
+
+        it "Then I got expected application in a list", ->
+            console.log @body
+            @body.rows.length.should.equal 2
+            @body.rows[1].name.should.equal "My App"
+

 describe "Users", ->


30 != 7 days

30 != 7 days

--- services/web/public/coffee/main/plans.coffee
+++ services/web/public/coffee/main/plans.coffee
@@ -60,7 +60,7 @@
 	App.controller "PlansController", ($scope, $modal, event_tracking, abTestManager, MultiCurrencyPricing) ->

 		buckets = [
-			{ bucketName:"7d", queryString: "_free_trial_7_days", trial_len:30 }
+			{ bucketName:"7d", queryString: "_free_trial_7_days", trial_len:7 }
 			{ bucketName:"14d", queryString: "_free_trial_14_days", trial_len:14 }
 		]
 		bucket = abTestManager.getABTestBucket "trial_len", buckets


log: fix case

log: fix case

--- workers/log/lib/log/models/elasticsearch.coffee
+++ workers/log/lib/log/models/elasticsearch.coffee
@@ -13,7 +13,7 @@

 {
   log           : { run }
-  elasticsearch : { host, port}
+  elasticSearch : { host, port}
 } = KONFIG

 config = {


moving requestAnimationFrame to gamelib

moving requestAnimationFrame to gamelib

--- test/browser_extensions.coffee
+++ test/browser_extensions.coffee
@@ -1,7 +0,0 @@
-module "window"
-
-test "requestAnimationFrame exists", ->
-  ok window.requestAnimationFrame
-
-module()
-


Fixed gameStats bug

Fixed gameStats bug

--- web/scripts/codeland.coffee
+++ web/scripts/codeland.coffee
@@ -102,6 +102,8 @@
     #Todo FADE IN

     description = root.getGameDescriptions()[game]
+
+
     env = {
         key: game
         description : description
@@ -112,7 +114,7 @@
         codeland : this
         backEnd: description.backEnd
         gameState: description.gameState
-        stats : root.getGameStats()
+        stats : root.loadGameStats(game)
     }
     #Not used ... window.location.hash='game='+encodeURIComponent(game)
     root.currentGame = new GameManager env
@@ -141,9 +143,8 @@
     root.setString(key, JSON.stringify val)
     return

-root.getGameStats = (gameKey) ->
+root.loadGameStats = (gameKey) ->
     p=root.getPlayer()
-    gameKey ?= p.currentGame
     data = p.games[gameKey] ?= {}
 #Ensure we have the minimum number of expect properties
     data.abortCount ?=0
@@ -329,11 +330,15 @@
     return

 root.addHintsToCode = (gameData) ->
+    gameData.code.initial?=''
     if gameData.code.comments
         # Also ensures newlines in the data are properly commented out
-        one = '// '+ ((gameData.code.comments.join('\n')).replace(/\n/g,'\n// '))
-        gameData.code.initial = one + '\n' + \
-            (if gameData.code.initial? then gameData.code.initial else '')
+        one = '// '+ ((gameData.code.comments.join('\n')).replace(/\n/g,'\n// '))+ '\n'
+        # Hints go in prefix if it exists, otherwise they are prepended to the main area
+        if gameData.code.prefix
+            gameData.code.prefix = one + gameData.code.prefix
+        else
+            gameData.code.initial = one + '\n' + gameData.code.initial
     return

 root.getGameDescriptions = ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/active_bounds.coffee
+++ src/active_bounds.coffee
@@ -27,7 +27,7 @@
         height 100

     # => bullet2 will be removed when it
-         goes outside the bou
+         goes outside unless

 ActiveBounds module
 @name ActiveBounds


make time table row even wider

make time table row even wider

because of x Minutes ago

--- app/assets/javascripts/app/models/ticket.js.coffee
+++ app/assets/javascripts/app/models/ticket.js.coffee
@@ -19,9 +19,9 @@
       { name: 'escalation_time',       display: 'Escalation',              type: 'time', null: true, style: 'width: 12%', class: 'escalation', parentClass: 'noTruncate' },
       { name: 'article_count',         display: 'Article#',  style: 'width: 12%' },
       { name: 'created_by_id',         display: 'Created by', relation: 'User', readonly: 1 },
-      { name: 'created_at',            display: 'Created', type: 'time', style: 'width: 90px', readonly: 1, parentClass: 'noTruncate' },
+      { name: 'created_at',            display: 'Created', type: 'time', style: 'width: 120px', readonly: 1, parentClass: 'noTruncate' },
       { name: 'updated_by_id',         display: 'Updated by', relation: 'User', readonly: 1 },
-      { name: 'updated_at',            display: 'Updated', type: 'time', style: 'width: 90px', readonly: 1, parentClass: 'noTruncate' },
+      { name: 'updated_at',            display: 'Updated', type: 'time', style: 'width: 120px', readonly: 1, parentClass: 'noTruncate' },
     ]

   uiUrl: ->


Emit the message too

Emit the message too

--- src/robot.coffee
+++ src/robot.coffee
@@ -195,7 +195,7 @@
         results.push listener.call(message)
         break if message.done
       catch error
-        @emit('error', error)
+        @emit('error', error, message)
         false
     if message not instanceof CatchAllMessage and results.indexOf(true) is -1
       @receive new CatchAllMessage(message)


Reset sticky component before leaving the page using Turbolinks


Fix emitting errors. Needed to make sure invokeErrorHandlers was invoked on robot

--- src/robot.coffee
+++ src/robot.coffee
@@ -60,9 +60,10 @@

     @errorHandlers = []

-    @on 'error', @invokeErrorHandlers
+    @on 'error', (err, msg) =>
+      @invokeErrorHandlers(err, msg)
     process.on 'uncaughtException', (err) =>
-      @invokeErrorHandlers(err)
+      @emit 'error', err

   # Public: Adds a Listener that attempts to match incoming messages based on
@@ -159,10 +160,15 @@
   # user emitted error events.
   #
   # err - An Error object.
-  #
-  # Returns nothing.
-  invokeErrorHandlers: (err) ->
-    errorHandler err for errorHandler in @errorHandlers
+  # msg - An optional Response object that generated the error
+  #
+  # Returns nothing.
+  invokeErrorHandlers: (err, msg) ->
+    for errorHandler in @errorHandlers
+     try
+       errorHandler(err, msg)
+     catch errErr
+       @logger.error "while invoking error handler: #{errErr}\n#{errErr.stack}"

   # Public: Adds a Listener that triggers when no other text matchers match.
   #
@@ -190,7 +196,6 @@
         break if message.done
       catch error
         @emit('error', error)
-        @logger.error "Unable to call the listener: #{error}\n#{error.stack}"
         false
     if message not instanceof CatchAllMessage and results.indexOf(true) is -1
       @receive new CatchAllMessage(message)


Reset sticky component before clicking back button on browser

--- app/assets/javascripts/foundation_extras.js.coffee
+++ app/assets/javascripts/foundation_extras.js.coffee
@@ -9,3 +9,5 @@
       $("[data-sticky]").foundation("destroy") if $("[data-sticky]").length

     $(document).on("page:before-unload", clearSticky)
+
+    window.addEventListener("popstate", clearSticky, false)


Remove debug output from PG.Brush#getRange

--- app/assets/javascripts/pgdatagraph/brush.js.coffee
+++ app/assets/javascripts/pgdatagraph/brush.js.coffee
@@ -45,7 +45,6 @@
     startIndex  = 0 if !startIndex? or startIndex < 0
     endIndex    = @domainValues.length - 1 if !endIndex? or endIndex < 0

-    console.log "PG.Brush#getRange: ", [@domainValues[startIndex], @domainValues[endIndex]]
     @options.rangeChanged @domainValues[startIndex], @domainValues[endIndex]

   updateCover: (event, ui) =>


Documentation and bugfixes

Documentation and bugfixes

--- cake/tasks/cordova.coffee
+++ cake/tasks/cordova.coffee
@@ -37,6 +37,7 @@
   emulateIOS:     => @_emulate 'ios'
   emulateAll:     => @_emulate @platforms...

+  # For initialize, get user input, build Cordova project, and copy generated assets
   _initialize: (callback) ->
     wrench.rmdirSyncRecursive @cordovaPath, ->
     args = ['create', @cordovaPath]
@@ -57,6 +58,8 @@
         args.push name if name isnt ''
         callback()

+    # After prompt and generation, copy config.xml and res into app/assets
+    #   before finishing up.
     packageNamePrompt =>
       @exec args, =>
         stream = fs.createReadStream("#{@cordovaPath}/www/config.xml")
@@ -68,14 +71,19 @@
             process.exit()
         stream.pipe(fs.createWriteStream('app/assets/config.xml'))

+  # NOTE: For the commands below it is required to be in the Cordova project folder.
+  #   Therefore we need to point the command to that folder and change the location
+  #   of the command to execute.
+
+  # After adding, drop in .gitignore files to the projects
   _add: (platforms...) ->
     platforms.unshift 'add'
     platforms.unshift 'platform'
     onExit = =>
       for platform in platforms when platform isnt 'add'
-        fs.createReadStream("./cake/gitignore/#{platform}.gitignore")
-          .pipe(fs.createWriteStream("#{@cordovaPath}/platforms/#{platform}/.gitignore"))
-      process.exit()
+        stream = fs.createReadStream("./cake/gitignore/#{platform}.gitignore")
+        stream.on 'end', -> process.exit()
+        stream.pipe(fs.createWriteStream("#{@cordovaPath}/platforms/#{platform}/.gitignore"))
     {command} = this
     @command = "../.#{@command}"
     @exec(platforms, onExit, cwd: @cordovaPath, env: process.env)
@@ -97,8 +105,16 @@
     @command = command

   _emulate: (platforms...) ->
-    platforms.unshift 'emulate'
+    afterBuild = =>
+      platforms.shift()
+      platforms.unshift 'emulate'
+      {command} = this
+      @command = "../.#{@command}"
+      @exec(platforms, (->), cwd: @cordovaPath)
+      @command = command
+
+    platforms.unshift 'build'
     {command} = this
     @command = "../.#{@command}"
-    @exec(platforms, (->), cwd: @cordovaPath)
+    @exec(platforms, afterBuild, cwd: @cordovaPath)
     @command = command