Deprecate the getInt and getPositiveInt methods

--- src/config.coffee
+++ src/config.coffee
@@ -207,56 +207,30 @@
     schema

   ###
-  Section: To Deprecate
-  ###
-
-  # Retrieves the setting for the given key as an integer.
-  #
-  # * `keyPath` The {String} name of the key to retrieve
-  #
-  # Returns the value from Atom's default settings, the user's configuration
-  # file, or `NaN` if the key doesn't exist in either.
+  Section: Deprecated
+  ###
+
   getInt: (keyPath) ->
+    deprecate '''Config::getInt is no longer necessary. Use ::get instead.
+    Make sure the config option you are accessing has specified an `integer`
+    schema. See the configuration section of
+    https://atom.io/docs/latest/creating-a-package for more info.'''
     parseInt(@get(keyPath))

-  # Retrieves the setting for the given key as a positive integer.
-  #
-  # * `keyPath` The {String} name of the key to retrieve
-  # * `defaultValue` The integer {Number} to fall back to if the value isn't
-  #                positive, defaults to 0.
-  #
-  # Returns the value from Atom's default settings, the user's configuration
-  # file, or `defaultValue` if the key value isn't greater than zero.
   getPositiveInt: (keyPath, defaultValue=0) ->
+    deprecate '''Config::getPositiveInt is no longer necessary. Use ::get instead.
+    Make sure the config option you are accessing has specified an `integer`
+    schema with `minimum: 1`. See the configuration section of
+    https://atom.io/docs/latest/creating-a-package for more info.'''
     Math.max(@getInt(keyPath), 0) or defaultValue

-  # Toggle the value at the key path.
-  #
-  # The new value will be `true` if the value is currently falsy and will be
-  # `false` if the value is currently truthy.
-  #
-  # * `keyPath` The {String} name of the key.
-  #
-  # Returns the new value.
   toggle: (keyPath) ->
+    deprecate 'Config::toggle is no longer supported. Please remove from your code.'
     @set(keyPath, !@get(keyPath))

-  ###
-  Section: Deprecated
-  ###
-
-  # Unobserve all callbacks on a given key.
-  # * `keyPath` The {String} name of the key to unobserve.
-  #
   unobserve: (keyPath) ->
     deprecate 'Config::unobserve no longer does anything. Call `.dispose()` on the object returned by Config::observe instead.'

-  # Push the value to the array at the key path.
-  #
-  # * `keyPath` The {String} key path.
-  # * `value` The value to push to the array.
-  #
-  # Returns the new array length {Number} of the setting.
   pushAtKeyPath: (keyPath, value) ->
     deprecate 'Please remove from your code. Config::pushAtKeyPath is going away. Please push the value onto the array, and call Config::set'
     arrayValue = @get(keyPath) ? []
@@ -264,12 +238,6 @@
     @set(keyPath, arrayValue)
     result

-  # Add the value to the beginning of the array at the key path.
-  #
-  # * `keyPath` The {String} key path.
-  # * `value` The value to shift onto the array.
-  #
-  # Returns the new array length {Number} of the setting.
   unshiftAtKeyPath: (keyPath, value) ->
     deprecate 'Please remove from your code. Config::unshiftAtKeyPath is going away. Please unshift the value onto the array, and call Config::set'
     arrayValue = @get(keyPath) ? []
@@ -277,12 +245,6 @@
     @set(keyPath, arrayValue)
     result

-  # Remove the value from the array at the key path.
-  #
-  # * `keyPath` The {String} key path.
-  # * `value` The value to remove from the array.
-  #
-  # Returns the new array value of the setting.
   removeAtKeyPath: (keyPath, value) ->
     deprecate 'Please remove from your code. Config::removeAtKeyPath is going away. Please remove the value from the array, and call Config::set'
     arrayValue = @get(keyPath) ? []


Remove +default junk

Remove +default junk

--- src/config.coffee
+++ src/config.coffee
@@ -685,7 +685,6 @@
       selector = name
       name = null

-    name = "#{name ? ''}+default"
     settingsBySelector = {}
     settingsBySelector[selector] = value
     @scopedSettingsStore.addProperties(name, settingsBySelector)


Troubleshoot: regressed unused @_modal variable from avatarareagroupswitcherpopup

--- client/Main/avatararea/avatarareagroupswitcherpopup.coffee
+++ client/Main/avatararea/avatarareagroupswitcherpopup.coffee
@@ -149,7 +149,7 @@
       cssClass   : 'bottom'
       partial    : 'System Health Check'
       click      : (event)=>
-        @_modal = new TroubleshootModal
+        new TroubleshootModal
         @hide()

     @avatarPopupContent.addSubView dashboardLink = new KDCustomHTMLView


remove state from non-component element'

--- client/apps/edit/components/section_image_set/index.coffee
+++ client/apps/edit/components/section_image_set/index.coffee
@@ -94,16 +94,18 @@
     e.preventDefault()
     val = @refs.byUrl.value
     slug = _.last(val.split '/')
-    @refs.byUrl.setState loading: true
+    $(@refs.byUrl).siblings('button').addClass('is-loading')
     new Artwork(id: slug).fetch
       error: (m, res) =>
-        @refs.byUrl.setState(
-          errorMessage: 'Artwork not found. Make sure your urls are correct.'
-          loadingUrls: false
-        ) if res.status is 404
+        if res.status is 404
+          $(@refs.byUrl).val('').attr('placeholder', 'Artwork not found')
+          setTimeout( =>
+            $(@refs.byUrl).siblings('button').removeClass('is-loading')
+            $(@refs.byUrl).attr('placeholder', 'Add artwork url')
+          , 3000)
       success: (artwork) =>
-        @refs.byUrl.setState loading: false, errorMessage: ''
-        $(@refs.byUrl).val ''
+        $(@refs.byUrl).removeClass('is-loading').val ''
+        $(@refs.byUrl).siblings('button').removeClass('is-loading')
         newImages = @state.images.concat [artwork.denormalized()]
         @setState images: newImages


Create unique keys for the terms in the multiword interface

--- app/assets/javascripts/rglossa/react/components/centre/search_inputs/cwb_multiword_input.jsx.coffee
+++ app/assets/javascripts/rglossa/react/components/centre/search_inputs/cwb_multiword_input.jsx.coffee
@@ -1,6 +1,22 @@
 #= require ./cwb_multiword_term

 ###* @jsx React.DOM ###
+
+# This will be used to create a unique ID for each query term component.
+# React wants a unique key for each component in a sequence, such as the set
+# of search inputs in the multiword interface, and it will mess up the text
+# in the search boxes when we remove a term from the query if we don't provide this.
+# Normally, the items in a list have some kind of database ID that we can use,
+# but query terms don't. Also, we cannot just use a hash code created from the
+# queryTerm object, since we may have several identical terms in a query.
+# Thus, in getInitialState() we create a list (called queryTermIds) containing a
+# unique ID number for each term in the initial query (i.e., the
+# one provided in the props when this component is mounted), and then we add a newly
+# created ID when adding a query term in the multiword interface and remove the
+# terms ID from the list when a term is removed. This is the kind of ugly state
+# manipulation that React normally saves us from, but in cases like this it seems
+# unavoidable...
+lastQueryTermId = 0

 createTerm = (overrides = {}) ->
   term =
@@ -28,11 +44,15 @@
     handleSearch: React.PropTypes.func.isRequired

   getInitialState: ->
-    queryTerms: @constructQueryTerms(@props.searchQuery)
+    queryTerms = @constructQueryTerms(@props.searchQuery)
+    queryTermIds = (lastQueryTermId++ for term in queryTerms)
+
+    queryTerms: queryTerms
+    queryTermIds: queryTermIds
+

   componentWillReceiveProps: (nextProps) ->
     @setState(queryTerms: @constructQueryTerms(nextProps.searchQuery))
-

   handleKeyDown: (e) ->
     if e.key is 'Enter'
@@ -117,12 +137,20 @@
   handleAddTerm: ->
     queryTerms = @state.queryTerms
     queryTerms.push(createTerm())
+    queryTermIds = @state.queryTermIds
+    queryTermIds.push(lastQueryTermId++)
+
+    @setState(queryTermIds: queryTermIds)
     @props.handleQueryChanged(@constructCQPQuery(queryTerms))

   handleRemoveTerm: (termIndex) ->
     queryTerms = @state.queryTerms
     queryTerms.splice(termIndex, 1)
+    queryTermIds = @state.queryTermIds
+    queryTermIds.splice(termIndex, 1)
+
+    @setState(queryTermIds: queryTermIds)
     @props.handleQueryChanged(@constructCQPQuery(queryTerms))

@@ -173,6 +201,7 @@
           {queryTerms.map(function(term, index) {
             return (
               <CwbMultiwordTerm
+                key={this.state.queryTermIds[index]}
                 term={term}
                 termIndex={index}
                 queryHasSingleTerm={this.state.queryTerms.length === 1}


Update tests according to new DOM structure.

--- test/front/coffee/suggest.spec.coffee
+++ test/front/coffee/suggest.spec.coffee
@@ -159,16 +159,32 @@

   it "10. should display preview", ->
     setFixtures '''
-

-

-

-

-

-
-
-
-

-

+

+

+

+
Upcoming...

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

       '''

     callbackResponse = {
@@ -198,19 +214,19 @@

     expect( $('#callbackNoResult').css('display') ).toEqual('none')

-    previewElement = $( '#subscription .preview' )
-
-    expect( $( previewElement[0] ).text() ).toContain( "title 1")
-    expect( $( previewElement[0] ).text() ).toContain( "19 04 2012 - 15:35")
-    expect( $( previewElement[0] ).text() ).toContain( "location 1")
-
-    expect( $( previewElement[1] ).text() ).toContain( "title 2")
-    expect( $( previewElement[1] ).text() ).toContain( "19 04 2012 - 15:35")
-    expect( $( previewElement[1] ).text() ).toContain( "location 2")
-
-    expect( $( previewElement[2] ).text() ).toContain( "title 3")
-    expect( $( previewElement[2] ).text() ).toContain( "19 04 2012 - 15:35")
-    expect( $( previewElement[2] ).text() ).toContain( "location 3")
+    previewElement = $('#previewEvents .pricing-table')
+
+    expect( $( previewElement[0] ).find(".price").text() ).toContain( "title 1")
+    expect( $( previewElement[0] ).find(".title").text() ).toContain( "19 04 2012 - 15:35")
+    expect( $( previewElement[0] ).find(".description").text() ).toContain( "location 1")
+
+    expect( $( previewElement[1] ).find(".price").text() ).toContain( "title 2")
+    expect( $( previewElement[1] ).find(".title").text() ).toContain( "19 04 2012 - 15:35")
+    expect( $( previewElement[1] ).find(".description").text() ).toContain( "location 2")
+
+    expect( $( previewElement[2] ).find(".price").text() ).toContain( "title 3")
+    expect( $( previewElement[2] ).find(".title").text() ).toContain( "19 04 2012 - 15:35")
+    expect( $( previewElement[2] ).find(".description").text() ).toContain( "location 3")

   it "11. should display fail", ->
     setFixtures '

'


Don't set sortProperties to null

Don't set sortProperties to null

Sorting could be triggered on controller and then when tab would be
switched, nullified sortProperties can result in an error.

--- assets/scripts/app/controllers/repos.coffee
+++ assets/scripts/app/controllers/repos.coffee
@@ -45,7 +45,7 @@
       content.forEach (r) -> r.updateTimes()

   activate: (tab, params) ->
-    @set('sortProperties', null)
+    @set('sortProperties', ['sortOrder'])
     tab ||= @get('defaultTab')
     @set('tab', tab)
     this["view#{$.camelize(tab)}"](params)
@@ -54,7 +54,6 @@
     @set('content', @get('recentRepos'))

   viewOwned: ->
-    @set('sortProperties', ['sortOrder'])
     @set('content', Travis.Repo.accessibleBy(@get('currentUser.login')))

   viewSearch: (params) ->


Send base64 of processed image as new icon to map

--- assets/js/gimme/gimme.coffee
+++ assets/js/gimme/gimme.coffee
@@ -39,6 +39,9 @@

 	Caman.remoteProxy = "proxy";

+	_onImageProcessed = ->
+		marker.setIcon @toBase64()
+
 	camanImage = Caman "#uploaded-marker"

 	Caman.Event.listen camanImage, "processStart", ->


IDE: Align requires in AppController.

IDE: Align requires in AppController.

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -1,41 +1,42 @@
+_                             = require 'lodash'
+$                             = require 'jquery'
+kd                            = require 'kd'
+nick                          = require 'app/util/nick'
 ndpane                        = require 'ndpane'
-_                             = require 'lodash'
-kd                            = require 'kd'
-$                             = require 'jquery'
+remote                        = require('app/remote').getInstance()
+KDView                        = kd.View
+whoami                        = require 'app/util/whoami'
+globals                       = require 'globals'
 kookies                       = require 'kookies'
-
+Machine                       = require 'app/providers/machine'
+IDEView                       = require './views/tabview/ideview'
+FSHelper                      = require 'app/util/fs/fshelper'
+showError                     = require 'app/util/showError'
+KDModalView                   = kd.ModalView
+KDSplitView                   = kd.SplitView
+IDEWorkspace                  = require './workspace/ideworkspace'
+IDEStatusBar                  = require './views/statusbar/idestatusbar'
+KodingKontrol                 = require 'app/kite/kodingkontrol'
+AppController                 = require 'app/appcontroller'
+IDEEditorPane                 = require './workspace/panes/ideeditorpane'
+IDEFileFinder                 = require './views/filefinder/idefilefinder'
+splashMarkups                 = require './util/splashmarkups'
+OnboardingEvent               = require 'app/onboarding/onboardingevent'
+IDEFilesTabView               = require './views/tabview/idefilestabview'
+IDETerminalPane               = require './workspace/panes/ideterminalpane'
+KDCustomHTMLView              = kd.CustomHTMLView
+KDSplitViewPanel              = kd.SplitViewPanel
+IDEStatusBarMenu              = require './views/statusbar/idestatusbarmenu'
+IDEContentSearch              = require './views/contentsearch/idecontentsearch'
+KDNotificationView            = kd.NotificationView
 KDBlockingModalView           = kd.BlockingModalView
-KDCustomHTMLView              = kd.CustomHTMLView
-KDModalView                   = kd.ModalView
-KDNotificationView            = kd.NotificationView
-KDSplitView                   = kd.SplitView
-KDSplitViewPanel              = kd.SplitViewPanel
-remote                        = require('app/remote').getInstance()
-globals                       = require 'globals'
-nick                          = require 'app/util/nick'
-showError                     = require 'app/util/showError'
-whoami                        = require 'app/util/whoami'
-Machine                       = require 'app/providers/machine'
-KodingKontrol                 = require 'app/kite/kodingkontrol'
-FSHelper                      = require 'app/util/fs/fshelper'
-AppController                 = require 'app/appcontroller'
+IDEApplicationTabView         = require './views/tabview/ideapplicationtabview'
+AceFindAndReplaceView         = require 'ace/acefindandreplaceview'
+environmentDataProvider       = require 'app/userenvironmentdataprovider'
 CollaborationController       = require './collaborationcontroller'
 VideoCollaborationController  = require './videocollaborationcontroller'
-IDEContentSearch              = require './views/contentsearch/idecontentsearch'
-IDEEditorPane                 = require './workspace/panes/ideeditorpane'
-IDEFileFinder                 = require './views/filefinder/idefilefinder'
-IDEFilesTabView               = require './views/tabview/idefilestabview'
-IDEStatusBar                  = require './views/statusbar/idestatusbar'
-IDEStatusBarMenu              = require './views/statusbar/idestatusbarmenu'
-IDETerminalPane               = require './workspace/panes/ideterminalpane'
-IDEView                       = require './views/tabview/ideview'
-IDEWorkspace                  = require './workspace/ideworkspace'
-splashMarkups                 = require './util/splashmarkups'
-IDEApplicationTabView         = require './views/tabview/ideapplicationtabview'
-AceFindAndReplaceView         = require 'ace/acefindandreplaceview'
 EnvironmentsMachineStateModal = require 'app/providers/environmentsmachinestatemodal'
-environmentDataProvider       = require 'app/userenvironmentdataprovider'
-OnboardingEvent               = require 'app/onboarding/onboardingevent'
+

 require('./routes')()


IDE: Reinit should show the MSM.

IDE: Reinit should show the MSM.

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -632,7 +632,9 @@
         switch event.status
           when Terminated then @handleMachineTerminated()

-      .on "reinit-#{machineItem._id}", @bound 'handleMachineReinit'
+      .on "reinit-#{machineItem._id}", (event) =>
+        @showStateMachineModal machineItem, event
+        @handleMachineReinit event

   handleMachineTerminated: ->


We want to update this once every chrome extension is updated, not now.

Also, we need to remove more than this (Factlink.Global.chrome_extension_id for instance

Revert "Updated chrome extension behaviour"

This reverts commit 9a6e7e1533d4834c6d28020d7e9a7cc73cbba5ea.

--- core/app/assets/javascripts/base/browser_extensions.coffee
+++ core/app/assets/javascripts/base/browser_extensions.coffee
@@ -1,2 +1,11 @@
-if not $('html').hasClass('chrome_extension_installed')
-  $('html').addClass('chrome_extension_not_installed')+detectChromeExtension = ->
+  s = document.createElement("script")
+  s.onerror = -> $("html").addClass "chrome_extension_not_installed"
+  s.onload = -> $("html").addClass "chrome_extension_installed"
+  document.body.appendChild s
+  s.src = "chrome-extension://#{Factlink.Global.chrome_extension_id}/manifest.json"
+
+if Factlink.Global.chrome_extension_id
+  is_chrome = navigator.userAgent.toLowerCase().indexOf("chrome") > -1
+  if is_chrome
+    detectChromeExtension()


cleanup

cleanup

--- js/src/util/security-utils.coffee
+++ js/src/util/security-utils.coffee
@@ -11,7 +11,7 @@

   wrapRequest = (request) ->
     request.beforeSend = (xhr) ->
-      {principal, credential} = CredentialLoader.getCredentials()
+      { principal, credential } = CredentialLoader.getCredentials()
       xhr.setRequestHeader(PRINCIPAL_COOKIE, principal)
       xhr.setRequestHeader(CREDENTIAL_COOKIE, credential)


test for this context in filter fn

test for this context in filter fn

--- test/spec/collection_view_spec.coffee
+++ test/spec/collection_view_spec.coffee
@@ -523,6 +523,7 @@
         basicSetup()
         filterer = sinon.spy (model, position) ->
           expect(model).to.be.a Model
+          expect(this).to.be collectionView
           expect(position).to.be.a 'number'
           true
         collectionView.filter filterer


:memo: Doc optional param and default property value

--- src/cursor.coffee
+++ src/cursor.coffee
@@ -114,9 +114,10 @@

   # Public: Get the RegExp used by the cursor to determine what a "word" is.
   #
-  # options: An {Object} with the following keys:
+  # options: An optional {Object} with the following keys:
   #   :includeNonWordCharacters - A {Boolean} indicating whether to include
   #                               non-word characters in the regex.
+  #                               (default: true)
   #
   # Returns a {RegExp}.
   wordRegExp: ({includeNonWordCharacters}={})->


Minor improvement to scaffold

Minor improvement to scaffold

--- cake/lib/scaffold.coffee
+++ cake/lib/scaffold.coffee
@@ -40,7 +40,7 @@

   promptString: ->
     name = _s.humanize(@className()).toLowerCase()
-    "\nEnter name for #{name}: "
+    "\nEnter name for #{name}:"

   validate: (name) ->
     true


Fix for artworks with multiple images + a default image

--- collections/artworks.coffee
+++ collections/artworks.coffee
@@ -18,9 +18,9 @@

   fillwidthDimensions: (maxHeight) ->
     imageWidths = @map (artwork) ->
-      return null unless image = artwork.get('images')[0]
-      width = Math.round maxHeight * image.aspect_ratio
-      height = if width < maxHeight then maxHeight else width / image.aspect_ratio
+      return null unless image = artwork.defaultImage()
+      width = Math.round maxHeight * image.get('aspect_ratio')
+      height = if width < maxHeight then maxHeight else width / image.get('aspect_ratio')
       { width: width, height: height }
     _.without imageWidths, null


Build, jenkins

Build, jenkins

--- app/assets/javascripts/oxalis/controller/viewmodes/plane_controller.coffee
+++ app/assets/javascripts/oxalis/controller/viewmodes/plane_controller.coffee
@@ -428,5 +428,4 @@
           curGlobalPos[1],
           curGlobalPos[2] - (constants.VIEWPORT_WIDTH * scaleFactor / 2 - clickPos.y) / scaleFactor * planeRatio[2] * zoomFactor ]

-
 module.exports = PlaneController


DatasetPositionView: store current view mode, Fixes #499

--- app/assets/javascripts/oxalis/view/action-bar/dataset_position_view.coffee
+++ app/assets/javascripts/oxalis/view/action-bar/dataset_position_view.coffee
@@ -17,7 +17,7 @@

-      <% if(isArbitrayMode()) { %>
+      <% if(isArbitrayMode) { %>

           Rotation
           <input id="trace-rotation-input" class="form-control" type="text" value="<%= rotation() %>">
@@ -28,16 +28,13 @@

   templateHelpers :
     position : ->
-      @vec3ToString(@flycam.position)
+      @vec3ToString(@flycam.getPosition())

     rotation : ->
-      @vec3ToString(@flycam3d.rotation)
+      @vec3ToString(@flycam3d.getRotation())

     vec3ToString : (vec3) ->
       return Math.floor(vec3[0]) + ", " + Math.floor(vec3[1]) + ", " + Math.floor(vec3[2])
-
-    isArbitrayMode : ->
-      return @controlMode in constants.MODES_ARBITRARY

   events :
     "change #trace-position-input" : "changePosition"
@@ -46,11 +43,25 @@

   initialize : (options) ->

-    @listenTo(app.vent, "changeViewMode", @render)
+    @viewMode = constants.MODE_PLANE_TRACING
+    @listenTo(app.vent, "changeViewMode", @updateViewMode)

     # TODO MEASURE PERFORMANCE HIT BECAUSE OF CONSTANT RE-RENDER
     @listenTo(@model.get("flycam3d"), "changed", @render)
     @listenTo(@model.get("flycam"), "positionChanged", @render)
+
+
+  serializeData : ->
+
+    console.log @viewMode
+    return _.extend(@model, {
+      isArbitrayMode : @viewMode in constants.MODES_ARBITRARY
+    })
+
+
+  updateViewMode : (@viewMode) ->
+
+    @render()

   changePosition : (event) ->


Add rejectOnDrop option to Cluster constructor

--- src/Cluster.coffee
+++ src/Cluster.coffee
@@ -1,9 +1,9 @@
 class Cluster
-	constructor: (@maxNb, @minTime, @highWater, @strategy) ->
+	constructor: (@maxNb, @minTime, @highWater, @strategy, @rejectOnDrop) ->
 		@limiters = {}
 		@Bottleneck = require "./Bottleneck"
 		@startAutoCleanup()
-	key: (key="") -> @limiters[key] ? (@limiters[key] = new @Bottleneck @maxNb, @minTime, @highWater, @strategy)
+	key: (key="") -> @limiters[key] ? (@limiters[key] = new @Bottleneck @maxNb, @minTime, @highWater, @strategy, @rejectOnDrop)
 	deleteKey: (key="") -> delete @limiters[key]
 	all: (cb) -> for own k,v of @limiters then cb v
 	keys: -> Object.keys @limiters


tweak, fewer empty output cells

tweak, fewer empty output cells

--- lib/console/console.coffee
+++ lib/console/console.coffee
@@ -136,7 +136,7 @@
   bufferOut: (item) ->
     {type, text} = item
     last = @lastOutput()
-    if last?.type is type and last.expires > performance.now()
+    if last?.type is type and (last.expires > performance.now() or not last.text)
       last.text += text
     else
       @output item


Fixes #66 as it was being specific that the current selector needed to be a namespace

--- lib/goto/class-goto.coffee
+++ lib/goto/class-goto.coffee
@@ -93,7 +93,7 @@

         if @$(selector).parent().hasClass('function argument') ||
            @$(selector).prev().hasClass('namespace') && @$(selector).hasClass('class') ||
-           @$(selector).next().hasClass('class')
+           @$(selector).next().hasClass('class') && @$(selector).hasClass('namespace')
             return @$(selector).parent().children('.namespace, .class:not(.operator):not(.constant)')

         if @$(selector).prev().hasClass('namespace') || @$(selector).next().hasClass('inherited-class')


Fix reporting of failing async tests

Fix reporting of failing async tests

This lets mocha handle uncaught exceptions thrown in async tests,
enabling the completion of a test suite with such errors.

--- lib/mocha-phantomjs.coffee
+++ lib/mocha-phantomjs.coffee
@@ -42,6 +42,7 @@
     @page.viewportSize = @config.viewportSize if @config.viewportSize
     @page.onConsoleMessage = (msg) -> system.stdout.writeLine(msg)
     @page.onError = (msg, traces) =>
+      return if @page.evaluate -> window.onerror?
       for {line, file}, index in traces
         traces[index] = "  #{file}:#{line}"
       @fail "#{msg}\n\n#{traces.join '\n'}"


Add vbd.setBootable api call. (fix vatesfr/xo-web/#583)

--- src/api/vbd.coffee
+++ src/api/vbd.coffee
@@ -88,6 +88,26 @@

 exports.set = set

+#---------------------------------------------------------------------
+
+setBootable = $coroutine ({vbd, bootable}) ->
+  xapi = @getXAPI vbd
+  { _xapiRef: ref } = vbd
+
+  yield xapi.call 'VBD.set_bootable', ref, bootable
+  return
+
+setBootable.params = {
+  vbd: { type: 'string' }
+  bootable: { type: 'boolean' }
+}
+
+setBootable.resolve = {
+  vbd: ['vbd', 'VBD', 'administrate'],
+}
+
+exports.setBootable = setBootable
+
 #=====================================================================

 Object.defineProperty(exports, '__esModule', {


Group lists together

Group lists together

by moving Modals down

--- lib/styleguide-view.coffee
+++ lib/styleguide-view.coffee
@@ -854,18 +854,6 @@

         '''

-      @exampleSection 'modal-panel', 'Modals', ->
-        @p => @raw 'Modals are like dialog boxes.'
-        @p => @raw '''
-          Supported classes: .from-top, .from-bottom and .floating.
-          .from-top will come down from the top of the UI, etc.
-        '''
-        @exampleHtml '''
-          <atom-panel class='modal'>
-
Some content

-          </atom-panel>
-        '''
-
       @exampleSection 'select-list', 'Select List', ->
         @p => @raw '''
           You wont be creating a .select-list directly, but will do so
@@ -1055,6 +1043,18 @@

         '''

+      @exampleSection 'modal-panel', 'Modals', ->
+        @p => @raw 'Modals are like dialog boxes.'
+        @p => @raw '''
+          Supported classes: .from-top, .from-bottom and .floating.
+          .from-top will come down from the top of the UI, etc.
+        '''
+        @exampleHtml '''
+          <atom-panel class='modal'>
+
Some content

+          </atom-panel>
+        '''
+
       @exampleSection 'tooltips', 'Tooltips', ->
         @p => @raw '''
           You do not create the markup directly. You call


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/bounded.coffee
+++ src/bounded.coffee
@@ -60,7 +60,7 @@
     width: 7
     height: 20

-  equals player.distance(enemy), Point.distance(player.position(), enemy.position())
+  equals player.distance(enemy)
   # => 92.19544457292888
   </pre></code>


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/bounded.coffee
+++ src/bounded.coffee
@@ -42,9 +42,9 @@

   closest: (selector) ->
     if Object.isString(selector)
-        selector = engine.find(selector)
-      else
-        selector = [].concat(groupA)
+      selector = engine.find(selector)
+    else
+      selector = [].concat(selector)

   ###*
   Distance between two objects. Proxies to Point.distance.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/bounded.coffee
+++ src/bounded.coffee
@@ -41,7 +41,7 @@
     collisionMargin: Point(0, 0)

   ###*
-  Distance between two objects. Proxies
+  Distance between two objects. Proxies to Point.distance.

   player = Core


Fixed facebook share url

Fixed facebook share url

--- app/client/game/result.coffee
+++ app/client/game/result.coffee
@@ -147,7 +147,7 @@
       #   object:window.location.href,
       # })
       method: 'share',
-      href: Meteor.absoluteUrl(Router.current().path.substr(1)),
+      href: Template.socialshare.url()
     }, (response) ->
       console.log(response)
     )


remove additional fields

remove additional fields

--- app/coffee/PackManager.coffee
+++ app/coffee/PackManager.coffee
@@ -294,7 +294,7 @@
 				return callback() if not historyPacks?
 				# select only the new packs not already in the index
 				newPacks = (pack for pack in historyPacks when not indexResult?[pack._id]?)
-				newPacks = (_.omit(pack, 'doc_id', 'project_id', 'n', 'sz') for pack in newPacks)
+				newPacks = (_.omit(pack, 'doc_id', 'project_id', 'n', 'sz', 'last_checked', 'finalised') for pack in newPacks)
 				if newPacks.length
 					logger.log {project_id, doc_id, n: newPacks.length}, "found new packs"
 				callback(null, newPacks)


Application class - clean up

Application class - clean up

--- app/coffee/application.coffee
+++ app/coffee/application.coffee
@@ -4,36 +4,14 @@
 # mobile device on which the app is running
 class Application extends Marionette.Service

+  # Starts the application immediately
   initialize: ->
-
-    # Listener for 'deviceready' event
-    # fired when the Cordova framework has successfully initialized
-    document.addEventListener 'deviceready', @onDeviceReady, false
-
-    # Starts application without 'deviceready' event
-    # Used while debugging the application in-browser
-    # window.ble is only defined when the app is running
-    # on a mobile device
-    # setTimeout( =>
-    #   @onDeviceReady() unless window.ble
-    # , 1000)
-
-    setTimeout( =>
-      @onDeviceReady() unless window.ble
-    , 100)
-
-    return true
+    @onReady()

   # Starts the application
   # Starts Backbone.history (enables routing)
-  # And initializes header and sidebar modules
-  onDeviceReady: ->
+  onReady: ->
     Backbone.history.start()
-    Backbone.Radio.channel('sidebar').trigger('initialize') # TODO - need sidebar?
-
-    # TODO - abstract into header component
-    $('.navbar-brand').on 'click', =>
-      Backbone.Radio.channel('sidebar').trigger('toggle')

 # # # # #


Debugging

Debugging

--- app/coffee/config/cors.coffee
+++ app/coffee/config/cors.coffee
@@ -22,3 +22,6 @@
     options.xhrFields = { withCredentials: true }

   return proxiedSync(method, model, options)
+
+
+# ext. 276-6904


Bug fixing

Bug fixing

--- mail.coffee
+++ mail.coffee
@@ -46,7 +46,7 @@
         DeleteByRecipient = (recipient) ->
             if mails = GetMail()[recipient.toLowerCase()]
                 for mail, index in mails by -1
-                    if mail[0] is msg.message.user.name.toLowercase()
+                    if mail[0] is msg.message.user.name.toLowerCase()
                         mails.splice index, 1
                         ++deleted

@@ -58,17 +58,17 @@
             else
                 msg.reply "#{deleted} of your mail(s) towards #{recipient} has been deleted."
                 robot.brain.save()
-            return

         # Delete all from sender / command executor
-        for recipient, mails of GetMail()
-            DeleteByRecipient recipient
+        else
+            for recipient, mails of GetMail()
+                DeleteByRecipient recipient

-        if deleted is 0
-            msg.reply "There are no outbound mail sent by you."
-        else
-            msg.reply "#{deleted} of your mail(s) has been deleted."
-            robot.brain.save()
+            if deleted is 0
+                msg.reply "There are no outbound mail sent by you."
+            else
+                msg.reply "#{deleted} of your mail(s) has been deleted."
+                robot.brain.save()

     robot.respond /mail (\S+) (.+)/i, (msg) ->
         [_command, recipient, message] = msg.match
@@ -77,18 +77,16 @@

         if sender is recipient
             msg.reply "Are you sure you want to send a mail to yourself? Sad."
-            false
-        if recipient is robot.name.toLowerCase()
+        else if recipient is robot.name.toLowerCase()
             msg.reply "Thanks, but no thanks! I do not need any mail."
-            false
-
-        try
-            GetMail()[recipient] or= []
-            GetMail()[recipient].push [sender, moment().unix(), message]
-            robot.brain.save()
-            msg.reply "Your mail has been prepared for #{recipient}."
-        catch error
-            return robot.logger.error error
+        else
+            try
+                GetMail()[recipient] or= []
+                GetMail()[recipient].push [sender, moment().unix(), message]
+                robot.brain.save()
+                msg.reply "Your mail has been prepared for #{recipient}."
+            catch error
+                robot.logger.error error

     #
     # Hubot events


Check inactivity will use "show_window_time" instead of Date.now()

--- main.coffee
+++ main.coffee
@@ -163,7 +163,7 @@
     check_inactivity = ->
         time = (Date.now() - activity_state.date)
         if ewin.isVisible() and current_dialog == 0
-            add_log_entry {active:false, date:Date.now()}
+            add_log_entry {active:false, date:show_window_time}
             render_all()

     last_check_inactivity_interval = setInterval check_inactivity, 60000 * 5
@@ -301,7 +301,7 @@
                     "
             else
                 are_you_working_message = "
-                    You've been distracted for #{format_time(time)}.\n\n
+                    You've been distracted for #{format_time(time_since_show_window)}.\n\n
                     Did you start working?"

         dialogs = [


remove comment block

remove comment block

--- chrome/src/gmailr.coffee
+++ chrome/src/gmailr.coffee
@@ -1,9 +1,3 @@
-###
-Gmailr v0.0.1
-Licensed under The MIT License
-
-Copyright 2012, James Yu, Joscha Feth
-###
 (($, window) ->

   class XHRWatcher


use a different timeout for backend

use a different timeout for backend

--- app/js/services/wallet.coffee
+++ app/js/services/wallet.coffee
@@ -16,6 +16,9 @@
     pendingRegistrations: {}

     current_account: null
+
+    #Long time
+    backendTimeout: 999999

     check_wallet_status : ()->
       @wallet_get_info().then (result) =>
@@ -248,7 +251,7 @@
         @wallet_api.account_transaction_history(account_name, "", 0, 0, -1)

     wallet_unlock: (password)->
-        @rpc.request('wallet_unlock', [@timeout, password]).then (response) =>
+        @rpc.request('wallet_unlock', [@backendTimeout, password]).then (response) =>
           response.result

     check_if_locked: ->


Increased font size in Atom

Increased font size in Atom

--- .atom/config.cson
+++ .atom/config.cson
@@ -10,7 +10,7 @@
       "solarized-light-syntax"
     ]
   editor:
-    fontSize: 12
+    fontSize: 11
     scrollPastEnd: true
     showIndentGuide: true
     showInvisibles: true


robots.txt: returning a simple one, to mitigate crawl errors

--- src/smc-hub/hub_http_server.coffee
+++ src/smc-hub/hub_http_server.coffee
@@ -98,6 +98,18 @@
             timeout = ms('100 days') # more than a year would be invalid
             res.setHeader('Cache-Control', "public, max-age='#{timeout}'")
             res.setHeader('Expires', new Date(Date.now() + timeout).toUTCString());
+
+    # robots.txt: disable indexing for published subdirectories, in particular to avoid a lot of 500/404 errors
+    router.use '/robots.txt', (req, res) ->
+        res.header("Content-Type", "text/plain")
+        res.header('Cache-Control', 'private, no-cache, must-revalidate')
+        res.write('''
+                  User-agent: *
+                  Disallow: /projects/*
+                  Disallow: /*/raw/
+                  Disallow: /*/port/
+                  ''')
+        res.end()

     # The /static content
     router.use '/static',


fix horrendous bugs in misc_node.execute_code...

--- src/smc-util-node/misc_node.coffee
+++ src/smc-util-node/misc_node.coffee
@@ -460,7 +460,7 @@
                 c()
         (c) ->
             if info?
-                fs.chmod(info.path, 0o777, c)
+                fs.chmod(info.path, 0o700, c)
             else
                 c()

@@ -566,11 +566,11 @@
         # TODO:  This is dangerous, e.g., it could print out a secret_token to a log file.
         # winston.debug("(time: #{walltime() - start_time}): Done running '#{opts.command} #{opts.args.join(' ')}'; resulted in stdout='#{misc.trunc(stdout,512)}', stderr='#{misc.trunc(stderr,512)}', exit_code=#{exit_code}, err=#{err}")
         # Do not litter:
-        if tmpfilename?
+        if info?.path?
             try
-                fs.unlink(tmpfilename)
+                fs.unlink(info.path)
             catch e
-                winston.debug("failed to unlink #{tmpfilename}")
+                winston.debug("failed to unlink #{info.path}")

         if opts.verbose


Removes erroneous comment about a keystore

--- src/test/debug/android_test.coffee
+++ src/test/debug/android_test.coffee
@@ -1,7 +1,5 @@
 # The `phonegap:debug:android` task will create a `releases/android` directory
 # containing a signed application package ready for distribution.
-#
-# You will need to specify a keystore file at `phonegap.config.key.store`.

 grunt = require 'grunt'
 pkg = grunt.file.readJSON 'package.json'


Speed up setting the hidden input at the same place as the cursor.

Order of magnitude speedup

--- src/editor.coffee
+++ src/editor.coffee
@@ -704,7 +704,12 @@
     @on 'cursor:moved', =>
       return unless @isFocused
       cursorView = @getCursorView()
-      @hiddenInput.offset(cursorView.offset()) if cursorView.isVisible()
+
+      if cursorView.isVisible()
+        # This is an order of magnitude faster than checking .offset().
+        style = cursorView[0].style
+        @hiddenInput[0].style.top = style.top
+        @hiddenInput[0].style.left = style.left

     selectedText = null
     @hiddenInput.on 'compositionstart', =>


Event docs

Event docs

--- src/editor.coffee
+++ src/editor.coffee
@@ -52,126 +52,6 @@
 #
 # **When in doubt, just default to buffer coordinates**, then experiment with
 # soft wraps and folds to ensure your code interacts with them correctly.
-#
-# ## Events
-#
-# ### path-changed
-#
-# Essential: Emit when the buffer's path, and therefore title, has changed.
-#
-# ### title-changed
-#
-# Essential: Emit when the buffer's path, and therefore title, has changed.
-#
-# ### modified-status-changed
-#
-# Extended: Emit when the result of {::isModified} changes.
-#
-# ### soft-wrap-changed
-#
-# Extended: Emit when soft wrap was enabled or disabled.
-#
-# * `softWrapped` {Boolean} indicating whether soft wrap is enabled or disabled.
-#
-# ### grammar-changed
-#
-# Extended: Emit when the grammar that interprets and colorizes the text has
-# been changed.
-#
-#
-#
-# ### contents-modified
-#
-# Essential: Emit when the buffer's contents change. It is emit asynchronously
-# 300ms after the last buffer change. This is a good place to handle changes to
-# the buffer without compromising typing performance.
-#
-# ### contents-conflicted
-#
-# Extended: Emitted when the buffer's underlying file changes on disk at a
-# moment when the result of {::isModified} is true.
-#
-# ### will-insert-text
-#
-# Extended: Emit before the text has been inserted.
-#
-# * `event` event {Object}
-#   * `text` {String} text to be inserted
-#   * `cancel` {Function} Call to prevent the text from being inserted
-#
-# ### did-insert-text
-#
-# Extended: Emit after the text has been inserted.
-#
-# * `event` event {Object}
-#   * `text` {String} text to be inserted
-#
-#
-#
-# ### cursor-moved
-#
-# Essential: Emit when a cursor has been moved. If there are multiple cursors,
-# it will be emit for each cursor.
-#
-# * `event` {Object}
-#   * `oldBufferPosition` {Point}
-#   * `oldScreenPosition` {Point}
-#   * `newBufferPosition` {Point}
-#   * `newScreenPosition` {Point}
-#   * `textChanged` {Boolean}
-#
-# ### cursor-added
-#
-# Extended: Emit when a cursor has been added.
-#
-# * `cursor` {Cursor} that was added
-#
-# ### cursor-removed
-#
-# Extended: Emit when a cursor has been removed.
-#
-# * `cursor` {Cursor} that was removed
-#
-#
-#
-# ### selection-screen-range-changed
-#
-# Essential: Emit when a selection's screen range changes.
-#
-# * `selection`: {Selection} object that has a changed range
-#
-# ### selection-added
-#
-# Extended: Emit when a selection's was added.
-#
-# * `selection`: {Selection} object that was added
-#
-# ### selection-removed
-#
-# Extended: Emit when a selection's was removed.
-#
-# * `selection`: {Selection} object that was removed
-#
-#
-#
-# ### decoration-added
-#
-# Extended: Emit when a {Decoration} is added to the editor.
-#
-# * `decoration` {Decoration} that was added
-#
-# ### decoration-removed
-#
-# Extended: Emit when a {Decoration} is removed from the editor.
-#
-# * `decoration` {Decoration} that was removed
-#
-# ### decoration-updated
-#
-# Extended: Emit when a {Decoration} is updated via the {Decoration::update} method.
-#
-# * `decoration` {Decoration} that was updated
-#
 module.exports =
 class Editor extends Model
   Serializable.includeInto(this)
@@ -292,62 +172,144 @@
     @displayBuffer.destroy()
     @languageMode.destroy()

+  ###
+  Section: Events
+  ###
+
+  # Essential: Calls your `callback` when the buffer's title has changed.
+  #
+  # * `callback` {Function}
   onDidChangeTitle: (callback) ->
     @emitter.on 'did-change-title', callback

+  # Essential: Calls your `callback` when the buffer's path, and therefore title, has changed.
+  #
+  # * `callback` {Function}
   onDidChangePath: (callback) ->
     @emitter.on 'did-change-path', callback

+  # Extended: Calls your `callback` when soft wrap was enabled or disabled.
+  #
+  # * `callback` {Function}
+  onDidChangeSoftWrapped: (callback) ->
+    @displayBuffer.onDidChangeSoftWrapped(callback)
+
+  # Extended: Calls your `callback` when the grammar that interprets and colorizes the text has
+  # been changed.
+  #
+  # * `callback` {Function}
+  onDidChangeGrammar: (callback) ->
+    @emitter.on 'did-change-grammar', callback
+
+  # Essential: Calls your `callback` when the buffer's contents change. It is
+  # emit asynchronously 300ms after the last buffer change. This is a good place
+  # to handle changes to the buffer without compromising typing performance.
+  #
+  # * `callback` {Function}
+  onDidStopChanging: (callback) ->
+    @getBuffer().onDidStopChanging(callback)
+
+  # Extended: Calls your `callback` when the result of {::isModified} changes.
+  #
+  # * `callback` {Function}
   onDidChangeModified: (callback) ->
     @getBuffer().onDidChangeModified(callback)

-  onDidChangeSoftWrapped: (callback) ->
-    @displayBuffer.onDidChangeSoftWrapped(callback)
-
-  onDidChangeGrammar: (callback) ->
-    @emitter.on 'did-change-grammar', callback
+  # Extended: Calls your `callback` when the buffer's underlying file changes on
+  # disk at a moment when the result of {::isModified} is true.
+  #
+  # * `callback` {Function}
+  onDidConflict: (callback) ->
+    @getBuffer().onDidConflict(callback)
+
+  # Extended: Calls your `callback` before text has been inserted.
+  #
+  # * `callback` {Function}
+  #   * `event` event {Object}
+  #     * `text` {String} text to be inserted
+  #     * `cancel` {Function} Call to prevent the text from being inserted
+  onWillInsertText: (callback) ->
+    @emitter.on 'will-insert-text', callback
+
+  # Extended: Calls your `callback` adter text has been inserted.
+  #
+  # * `callback` {Function}
+  #   * `event` event {Object}
+  #     * `text` {String} text to be inserted
+  onDidInsertText: (callback) ->
+    @emitter.on 'did-insert-text', callback
+
+  # Extended: Calls your `callback` when a {Cursor} is added to the editor.
+  #
+  # * `callback` {Function}
+  #   * `cursor` {Cursor} that was added
+  onDidAddCursor: (callback) ->
+    @emitter.on 'did-add-cursor', callback
+
+  # Extended: Calls your `callback` when a {Cursor} is removed to the editor.
+  #
+  # * `callback` {Function}
+  #   * `cursor` {Cursor} that was removed
+  onDidRemoveCursor: (callback) ->
+    @emitter.on 'did-remove-cursor', callback
+
+  # Essential: Calls your `callback` when a {Cursor} is removed to the editor.
+  #
+  # * `callback` {Function}
+  #   * `event` {Object}
+  #     * `oldBufferPosition` {Point}
+  #     * `oldScreenPosition` {Point}
+  #     * `newBufferPosition` {Point}
+  #     * `newScreenPosition` {Point}
+  #     * `textChanged` {Boolean}
+  onDidMoveCursor: (callback) ->
+    @emitter.on 'did-move-cursor', callback
+
+  # Extended: Calls your `callback` when a {Selection} is added to the editor.
+  #
+  # * `callback` {Function}
+  #   * `selection` {Selection} that was added
+  onDidAddSelection: (callback) ->
+    @emitter.on 'did-add-selection', callback
+
+  # Extended: Calls your `callback` when a {Selection} is removed from the editor.
+  #
+  # * `callback` {Function}
+  #   * `selection` {Selection} that was removed
+  onDidRemoveSelection: (callback) ->
+    @emitter.on 'did-remove-selection', callback
+
+  # Essential: Calls your `callback` when a selection's screen range changes.
+  #
+  # * `callback` {Function}
+  #   * `selection` {Selection} that moved
+  onDidChangeSelectionScreenRange: (callback) ->
+    @emitter.on 'did-change-selection-screen-range', callback
+
+  # Extended: Calls your `callback` with each {Decoration} added to the editor.
+  # Calls your `callback` immediately for any existing decorations.
+  #
+  # * `callback` {Function}
+  #   * `decoration` {Decoration}
+  observeDecorations: (callback) ->
+    @displayBuffer.observeDecorations(callback)
+
+  # Extended: Calls your `callback` when a {Decoration} is added to the editor.
+  #
+  # * `callback` {Function}
+  #   * `decoration` {Decoration} that was added
+  onDidAddDecoration: (callback) ->
+    @displayBuffer.onDidAddDecoration(callback)
+
+  # Extended: Calls your `callback` when a {Decoration} is removed from the editor.
+  #
+  # * `callback` {Function}
+  #   * `decoration` {Decoration} that was removed
+  onDidRemoveDecoration: (callback) ->
+    @displayBuffer.onDidRemoveDecoration(callback)

   onDidChangeCharacterWidths: (callback) ->
     @displayBuffer.onDidChangeCharacterWidths(callback)
-
-  onDidStopChanging: (callback) ->
-    @getBuffer().onDidStopChanging(callback)
-
-  onDidConflict: (callback) ->
-    @getBuffer().onDidConflict(callback)
-
-  onWillInsertText: (callback) ->
-    @emitter.on 'will-insert-text', callback
-
-  onDidInsertText: (callback) ->
-    @emitter.on 'did-insert-text', callback
-
-  onDidAddCursor: (callback) ->
-    @emitter.on 'did-add-cursor', callback
-
-  onDidRemoveCursor: (callback) ->
-    @emitter.on 'did-remove-cursor', callback
-
-  onDidMoveCursor: (callback) ->
-    @emitter.on 'did-move-cursor', callback
-
-  onDidAddSelection: (callback) ->
-    @emitter.on 'did-add-selection', callback
-
-  onDidRemoveSelection: (callback) ->
-    @emitter.on 'did-remove-selection', callback
-
-  onDidChangeSelectionScreenRange: (callback) ->
-    @emitter.on 'did-change-selection-screen-range', callback
-
-  observeDecorations: (callback) ->
-    @displayBuffer.observeDecorations(callback)
-
-  onDidAddDecoration: (callback) ->
-    @displayBuffer.onDidAddDecoration(callback)
-
-  onDidRemoveDecoration: (callback) ->
-    @displayBuffer.onDidRemoveDecoration(callback)

   onDidChangeScreenLines: (callback) ->
     @emitter.on 'did-change-screen-lines', callback


Home: added status link

Home: added status link

--- client/Intro/home.coffee
+++ client/Intro/home.coffee
@@ -242,6 +242,7 @@
           University
           Jobs
           Blog
+          Status

     """


datadog: add options parameter to sendDataDogEvent

--- client/Main/utils.coffee
+++ client/Main/utils.coffee
@@ -855,10 +855,14 @@
     parts.join ''

-  sendDataDogEvent: (eventName)->
+  sendDataDogEvent: (eventName, options = {})->
+
+    options.eventName = eventName

     sendEvent = (logs)->
-      KD.remote.api.DataDog.sendEvent { eventName, logs }
+
+      options.logs = logs
+      KD.remote.api.DataDog.sendEvent options

     kdlogs = KD.parseLogs()


remove noisy console

remove noisy console

--- client/app/router.coffee
+++ client/app/router.coffee
@@ -59,10 +59,10 @@
         unless app[name].length > 0
             collection.fetch
                 success: (collection, response, options) ->
-                    console.log collection
-                    console.log "Fetch: success"
+                    # console.log collection
+                    # console.log "Fetch: success"
                 error: ->
-                    console.log "Fetch: error"
+                    # console.log "Fetch: error"
         else
             collection.reset app[name].toJSON()


dt compiler has now own npm package

dt compiler has now own npm package

--- src/compile.coffee
+++ src/compile.coffee
@@ -1,311 +0,0 @@
-fs = require 'fs'
-jQuery = require 'jquery'
-{ isArray } = Array
-#{ trim, extend:merge } = jQuery
-#deep_merge (objs...) -> merge true, {}, objs...
-
-# /** PrivateConstants: DOM Element Type Constants
-#  *  DOM element types.
-#  *
-#  *  ElementType.NORMAL - Normal element.
-#  *  ElementType.TEXT - Text data element.
-#  *  ElementType.FRAGMENT - XHTML fragment element.
-#  */
-DOMElementType =
-    NORMAL:   1
-    TEXT:     3
-    CDATA:    4
-    FRAGMENT: 11
-
-##
-# copy many objects into one
-deep_merge = (objs...) ->
-    objs = objs[0] if isArray(objs[0])
-    res = {}
-    for obj in objs
-        for k, v of obj
-            if typeof(v) is 'object' and not isArray(v)
-                res[k] = deep_merge(res[k] or {}, v)
-            else
-                res[k] = v
-    res
-
-##
-# slice off spaces
-trim = (str) ->
-    str.replace /^\s+|\s+$/g, ""
-
-##
-# dom attributes to object
-slim_attrs = (el) ->
-    attrs = {}
-    for attr in el.attributes ? []
-        attrs[attr.name] = attr.value
-    attrs
-
-##
-# dom element to object
-slim = (el) ->
-    name:     el.nodeName.toLowerCase()
-    attrs:    slim_attrs(el)
-    children: traverse(el.childNodes)
-
-##
-# build children list
-traverse = (elems) ->
-    return [] unless elems?
-    res = []
-    for el in elems
-        if el.nodeType is DOMElementType.NORMAL
-            res.push slim el
-        else if el.nodeType is DOMElementType.TEXT
-            if trim(el.value).length
-                res.push el.value
-        else continue
-    return res
-
-##
-# build an object (json-able) from dom
-jsonify = (elems) ->
-    slim el for el in elems ? []
-
-##
-# copy only structure and reuse objects
-# except for the dom element objects (because of the children)
-copy_structure = (tree) ->
-    res = []
-    for el in tree ? []
-        if typeof el is 'string' or typeof el is 'number'
-            res.push el
-            continue
-        res.push
-            name:     el.name
-            attrs:    el.attrs
-            children: copy_structure(el.children)
-    return res
-
-##
-# tests a tag against the dom information from jsonify
-match = (tag, el) ->
-    return yes unless el? # nothing to test against
-    return no if tag.name isnt el.name
-    for key, value of tag.attrs
-        return no if el.attrs[key] isnt value
-    return yes
-
-##
-# create a new tag (and children) from data structure
-new_tag = (parent, el, callback) ->
-    attrs = deep_merge el.attrs # copy data
-    parent.tag el.name, attrs, ->
-        for child in el.children.slice() ? []
-            if typeof child is 'string' or typeof child is 'number'
-                @text "#{child}", append:on
-            else
-                new_tag this, child, ->
-                    callback?()
-                    callback = null
-        @end()
-        # call back some delayed work
-        callback?()
-
-##
-# apply possible additions from the data structure on the tag
-mask = (tag, el) ->
-    return unless el?
-    # no need to set tag.name because its the most important trigger for a match
-    tag.attr el.attrs # object
-    tag._elems = el.children
-
-##
-# this hooks on new instanziated templates and tries to
-# complete the structure with the given html design
-hook = (tpl) ->
-    tpl.xml.use (parent, tag, next) ->
-        elems = parent._elems
-
-        # when this is a tag created from data structure
-        return next(tag) unless elems?
-
-        repeat = ->
-            el = elems[0]
-
-            if typeof el is 'string' or typeof el is 'number'
-                console.log "text".blue, el
-                elems.shift() # rm text
-                parent.text? el, append:on
-                do repeat
-
-            else if match tag, el
-                console.log "match".yellow, el?.name, el?.attrs
-                elems.shift() # rm el
-                mask tag, el # apply
-                next(tag)
-
-            else # create new tag
-                console.log "new".green, el?.name, el?.attrs, "(",tag.name, tag.attrs,")"
-                # get the last pending tag out cuz we want to insert a new one in front
-                #last = parent.pending.pop()
-                # create and insert the new tag from el and delay work
-                console.log "WTF WTF WTF WTF".bold.red, tag.name, tag.attrs
-                new_tag parent, el, ->
-                    console.log "repeat".red, tag.name, tag.attrs, "(",el?.name, el?.attrs,")"
-                    do repeat
-                # put it back
-                #parent.pending.push(last) if last?
-        do repeat
-
-##
-# this add the data structure to the new instanziated template and hooks it
-suitup = (rawtemplate, tree) ->
-    return (args...) ->
-        tpl = rawtemplate args...
-        # local copy of the data structure
-        elems = copy_structure tree
-        # nest the data tree in the root
-        tpl.xml._elems = elems
-        # we need to get between the events from the builder and
-        # the output to change to events bahavior (inserting events before others)
-        hook tpl
-        # done
-        return tpl
-
-
-class HTMLCompiler
-    constructor: ->
-        # new jquery context
-        @$ = jQuery.create()
-        #aliases
-        @loadSync = @open
-
-    read: (filename, callback) ->
-        fs.readFile filename, (err, data) =>
-            callback?.call(this, err, data?.toString())
-
-    readSync: (filename) ->
-        fs.readFileSync(filename)?.toString()
-
-    parse: (data) ->
-        @el = @$(data)
-
-    select: (from, to) ->
-        # selector
-        el = @el.find(from)
-        # dont touch origin
-        el = el.clone()
-        # deselector
-        el.find(to).remove()
-        # rest
-        return el
-
-    use: (data) ->
-        @loaded = yes
-        @parse data
-
-    load: (filename, callback) ->
-        @read filename, (err, data) ->
-            return callback.call(this, err) if err
-            @use data
-            callback(null, @el)
-
-    open: (filename) ->
-        data = @readSync filename
-        return @use data
-
-    compile: (rawtemplate, el) ->
-        throw new Error "no html file loaded or html string used." unless @loaded
-        console.log "building data structure ..."
-        el ?= @el
-        # get only the important information
-        tree = jsonify el
-        console.log "design is ready loaded. suiting up the template ..."
-        r = suitup rawtemplate, tree
-        r.tree = tree
-        console.log "done."
-        r
-
-
-
-
-      ###`

-

-

-

-              2
-

-

-              vera@buddycloud.com
-              What a wonderful day
-

-
`###
-
-
-
-require 'colors'
-util = require('util')
-util.orginspect = util.inspect
-util.inspect = require('eyes').inspector(stream: null, hexy:{format:'fours'})
-{ Template, Tag } = require './dynamictemplate'
-#$ = jQuery
-#select=(e,from,to)->el=e.find(from).clone();el.find(to).remove();el
-#el=jQuery(require('fs').readFileSync('/home/dodo/code/arbyt/buddycloud/webclient/brunch/build/streams.html').toString())
-#x = select el, '#channels', '.channel'
-
-#(t = (T = (C = require('./src/compile')).example2())()).on 'data', console.log
-
-example = ->
-    design = new HTMLCompiler
-    console.log "* loading html ..."
-    streamshtml = design.open '/home/dodo/code/arbyt/buddycloud/webclient/brunch/build/streams.html'#"streams.html"
-
-    console.log "* selecting part of the html ..."
-    channelsdesign = streamshtml.select('#channels','.channel')
-    console.log "S", streamshtml.select('.channel')?.length
-        #.append(streamshtml.select('.channel')?.get(0).clone())
-
-    rawtemplate = (ee) ->
-        return new Template schema:5, ->
-            @$div id:'channels', ->
-                @$div ->
-                    ee?.on 'new:channel', (channel) =>
-                        @$div class:'channel', ->
-                            @$div class:'avatar', style:"background-image:url(#{channel.avatar})", ->
-                                @$span class:'counter', (channel.counter) # channelpost class should autofill
-                            @$div class:'info', ->
-                                @$span class:'owner', ->
-                                    jid = channel.get('jid')?.split('@') or []
-                                    @text "#{jid[0]}"
-                                    @$span class:'domain', "#{jid[1]}"
-                                @$span class:'status', (channel.nodes.get('status')?.last() or "")
-
-    console.log "* start compiling ..."
-    template = design.compile rawtemplate, channelsdesign # the rawtemplate gets autfilled with the selected designstuff
-
-
-example2  = ->
-    design = new HTMLCompiler
-    console.log "* using html ..."
-    design.use """
-

-
some linked logo

-

-
some random entry

-

-

-    """
-
-    rawtemplate = () ->
-        return new Template schema:5, ->
-            @$div id:'main', ->
-                # skipping logo, because that should be automagically added
-                @$div class:'list', ->
-                    @$div class:'entry', "specific text"
-
-    console.log "* start compiling ..."
-    template = design.compile rawtemplate
-    template.design = design
-    template
-
-#module.exports = {select, slim, slim_attrs, el, x, $, example, HTMLCompiler}
-module.exports = {example, example2, HTMLCompiler, copy_structure}
-


Ensure assignment to ~'coffeelib' still works

Like the rest of the helper functions ('extend', 'hasProp', ...), there
should be a test to ensure coffeescript can assign to properties named
similar to "coffeelib".

--- test/assignment.coffee
+++ test/assignment.coffee
@@ -561,9 +561,10 @@
     extend = 3
     hasProp = 4
     value: 5
-    method: (bind, bind1) => [bind, bind1, extend, hasProp, @value]
+    coffeelib: 6
+    method: (bind, bind1) => [bind, bind1, extend, hasProp, @value, @coffeelib]
   {method} = new B
-  arrayEq [1, 2, 3, 4, 5], method 1, 2
+  arrayEq [1, 2, 3, 4, 5, 6], method 1, 2

   modulo = -1 %% 3
   eq 2, modulo


renderables

renderables

--- src/context.coffee
+++ src/context.coffee
@@ -98,18 +98,11 @@
       handle_any_object
     ]

-    output = ($target) ->
-      (output) ->
-        $target.removeClass 'clean'
-        $item = $ '
'
-        if output?
-          $item.append output
-        $target.append $item
-        $item

     run_context = _.extend {}, extra_contexts...,
+      $el: $el
       current_options: {}
-      output: output $el
+      renderables: []
       active_context: ->
         console.warn 'no active running context. did you call an async function without keeping the context?' unless running_context_binding?
         run_context.current_context
@@ -148,6 +141,16 @@
         ->
           restoring_context fn, arguments

+      add_renderable: (renderable) ->
+        run_context.current_context.renderables.push renderable
+
+      output: (output) ->
+        $item = $ '
'
+        if output?
+          $item.append output
+        run_context.add_renderable _lead_render: -> $item
+        $item
+
       render: (o) ->
         @nested 'renderable', handle_renderable, o
         # TODO warn if not renderable
@@ -161,10 +164,14 @@
         @nested_item $item, fn, args...

       nested_item: ($item, fn, args...) ->
-        @output $item
+        nested_renderables = []
+        run_context.add_renderable _lead_render: ->
+          children = _.map nested_renderables, (i) -> i._lead_render()
+          $item.append children
+          $item

         nested_context = _.extend {}, run_context,
-          output: output $item
+          renderables: nested_renderables
         run_context.in_context nested_context, fn, args

       handle_exception: (e, compiled) ->
@@ -239,6 +246,8 @@
       `with (context_scope) {`
       result = (-> eval string).call run_context
       `}`
+      run_context.$el.removeClass 'clean'
+      run_context.$el.append _.map run_context.renderables, (r) -> r._lead_render()
       run_context.display_object result
     catch e
       run_context.handle_exception e, string


test($filter): fix #49, add test-case(use keyword) for parse condition.

--- test/odata.query.filter.coffee
+++ test/odata.query.filter.coffee
@@ -36,6 +36,14 @@
           return done(err)  if(err)
           res.body.value.length.should.be.equal(1)
           res.body.value[0].title.should.be.equal(books[1].title)
+          done()
+    it 'should filter items when field has keyword', (done) ->
+      request("http://localhost:#{PORT}")
+        .get("/book?$filter=author eq 'Ralls, Kim'")
+        .expect(200)
+        .end (err, res) ->
+          return done(err)  if(err)
+          res.body.value.length.should.be.equal(1)
           done()
     it 'should filter items when it has extra spaces at begin', (done) ->
       request("http://localhost:#{PORT}")


Skip and document 2 failing tests on phantomjs2

--- test/core.tests.coffee
+++ test/core.tests.coffee
@@ -186,7 +186,8 @@
         stdout.should.match /^mocha=UserAgent/

     describe 'cookies', ->
-      it 'has passed cookies', ->
+      # https://github.com/nathanboktae/mocha-phantomjs-core/issues/4
+      it 'has passed cookies', !process.env.PHANTOMJS2 and ->
         { stdout } = yield run
           test: 'cookie'
           cookies: [
@@ -243,7 +244,8 @@
         stdout.should.not.match /\u001b\[\d\dm/

     describe 'bail', ->
-      it 'should bail on the first error', ->
+      # https://github.com/nathanboktae/mocha-phantomjs-core/issues/3
+      it 'should bail on the first error', process.env.PHANTOMJS2 and ->
         { stdout } = yield run
           test: 'mixed'
           bail: true


mkdir: test string argument

mkdir: test string argument

--- test/core/mkdir.coffee
+++ test/core/mkdir.coffee
@@ -10,17 +10,16 @@

   scratch = test.scratch @

-  they 'should create dir', (ssh, next) ->
-    source = "#{scratch}/a_dir"
+  they 'as a directory option or as a string', (ssh, next) ->
     mecano
       ssh: ssh
-    .mkdir
-      directory: source
-    , (err, created) ->
+    .mkdir directory: "#{scratch}/a_dir", (err, created) ->
       created.should.be.true()
-    .mkdir
-      directory: source
-    , (err, created) ->
+    .mkdir directory: "#{scratch}/a_dir", (err, created) ->
+      created.should.be.false()
+    .mkdir "#{scratch}/b_dir", (err, created) ->
+      created.should.be.true()
+    .mkdir "#{scratch}/b_dir", (err, created) ->
       created.should.be.false()
     .then next

@@ -192,5 +191,3 @@
           return next err if err
           misc.mode.stringify(stat.mode).should.eql '40744'
           next()
-
-


Fix typo, closes #94

Fix typo, closes #94

--- app/translations/en_us.coffee
+++ app/translations/en_us.coffee
@@ -829,7 +829,7 @@

A resting coyote is captured by a Urban Wildlife Institute camera trap southwest of Chicago. Coyotes are among the most common carnivores in the Chicagoland area.

-
© Urban Wildlife Institute/Linco]ln Park Zoo

+
© Urban Wildlife Institute/Lincoln Park Zoo

This image shows a raccoon and opossum in the city of Chicago. Exploring the relationship between urban species is one goal of Chicago Wildlife Watch.


Cleanup

Cleanup

--- test/mycha_spec.coffee
+++ test/mycha_spec.coffee
@@ -118,14 +118,12 @@

       it 'loads Mychas test helper before the test files', ->
         helper_index = @mocha_argument.indexOf path.resolve 'lib/test_helper.coffee'
-        first_test_index = @mocha_argument.indexOf path.resolve 'test_data/two_tests/test/one_test.coffee'
         second_test_index = @mocha_argument.indexOf path.resolve 'test_data/two_tests/test/two_test.coffee'
-        expect(helper_index).to.be.lessThan first_test_index
         expect(helper_index).to.be.lessThan second_test_index

       it 'runs only the given test', ->
         expect(@mocha_argument).to.include_test_file 'two_tests', 'two_test.coffee'

       it 'does not provide any other arguments than listed above plus the done callback', ->
-        expect(@mocha_argument.length).to.equal 8
+        expect(@mocha_argument.length).to.equal 7


Fix background mismatch between MenuState, PlayState.

--- src/defines.coffee
+++ src/defines.coffee
@@ -17,7 +17,7 @@
     gameH: 600

     artH: 2912
-    mapH: 3152 # +240
+    mapH: 3136 # +224

     buttonW: 80
     buttonH: 30


split up result handling

split up result handling

--- src/notebook.coffee
+++ src/notebook.coffee
@@ -22,15 +22,43 @@
   forwards = +1
   backwards = -1

+  # predicates for cells
   is_input = (cell) -> cell.type is 'input'
   is_output = (cell) -> cell.type is 'output'
   is_clean = (cell) -> cell.is_clean()
   visible = (cell) -> cell.visible
   identity = (cell) -> true

+
+  # statement result handlers. return truthy if handled.
+  ignored = (object) -> object == ignore
+
+  handle_cli_cmd = (object) ->
+    if object?._lead_op?
+      object._lead_op.cli_fn.apply(@)
+      true
+
+  handle_lead_node = (object) ->
+    if lead.is_lead_node object
+      lead_string = lead.to_string object
+      if $.type(object) == 'function'
+        @cli.text "#{lead_string} is a Graphite function"
+        run_in_info_context @input_cell, "docs #{object.values[0]}"
+      else
+        @cli.text "What do you want to do with #{lead_string}?"
+        for f in ['data', 'graph', 'img', 'url']
+          @cli.example "#{f} #{object.to_js_string()}"
+      true
+
+  handle_any_object = (object) ->
+    @cli.object object
+    true
+
+
   init_codemirror = ->
     CodeMirror.keyMap.lead = ed.key_map
     $.extend CodeMirror.commands, ed.commands
+

   create_notebook = ->
     cells: []
@@ -275,6 +303,7 @@
     notebook = input_cell.notebook
     run_context =
       cell: output_cell
+      input_cell: input_cell
       notebook: notebook
       ops: ops
       current_options: {}
@@ -353,6 +382,17 @@
       run_context.output $pre
       run_context.failure()

+    result_handlers =[
+      ignored,
+      handle_cli_cmd,
+      handle_lead_node,
+      handle_any_object
+    ]
+
+    display_object = (object) ->
+      for handler in result_handlers
+        return if handler.call run_context, object
+
     lead.define_functions functions, graphite_function_names
     try
       compiled = CoffeeScript.compile(string, bare: true) + "\n//@ sourceURL=console-coffeescript.js"
@@ -367,20 +407,7 @@
         `with (cli) { with (functions) { with (vars) {`
         result = eval compiled
         `}}}`
-        unless result == ignore
-          if result?._lead_op?
-            result._lead_op.cli_fn.apply(run_context)
-          else if lead.is_lead_node result
-            lead_string = lead.to_string result
-            if $.type(result) == 'function'
-              cli.text "#{lead_string} is a Graphite function"
-              run_in_info_context input_cell, "docs #{result.values[0]}"
-            else
-              cli.text "What do you want to do with #{lead_string}?"
-              for f in ['data', 'graph', 'img', 'url']
-                cli.example "#{f} #{result.to_js_string()}"
-          else
-            cli.object result
+        display_object result
       catch e
         handle_exception e, compiled


Replaced fs.exists with isThere

Replaced fs.exists with isThere

--- src/fs-plus.coffee
+++ src/fs-plus.coffee
@@ -1,6 +1,7 @@
 fs = require 'fs'
 Module = require 'module'
 path = require 'path'
+isThere = require 'is-there'

 _ = require 'underscore-plus'
 async = require 'async'
@@ -108,7 +109,7 @@
   # Public: Asynchronously checks that the given path exists and is a directory.
   isDirectory: (directoryPath, done) ->
     return done(false) unless isPathValid(directoryPath)
-    fs.exists directoryPath, (exists) ->
+    isThere directoryPath, (exists) ->
       if exists
         fs.stat directoryPath, (error, stat) ->
           if error?


subset of the given attr matches too

subset of the given attr matches too

--- src/linker.coffee
+++ src/linker.coffee
@@ -34,7 +34,9 @@
     return yes unless el? # nothing to test against
     return no if tag.name isnt el.name
     for key, value of tag.attrs
-        return no if el.attrs[key] isnt value
+        if el.attrs[key] isnt value
+            return no if typeof value isnt 'string' or
+                        (el.attrs[key]?.indexOf(value) or -1) is -1
     return yes

 ##


Increase delay between retries in backoff

--- backend/remote/watcher.coffee
+++ backend/remote/watcher.coffee
@@ -105,8 +105,8 @@
             setTimeout (=> @whenReady callback), 100

     # When the replication fails, wait before trying again.
-    # For the first error, we wait between 1s and 2s.
-    # For next errors, the duration roughly doubles.
+    # For the first error, we wait between 2s and 4s.
+    # For next errors, it's 4 times longer.
     # After 5 errors, we give up.
     # TODO tests
     backoff: (err, fail, retry) =>
@@ -118,7 +118,7 @@
             fail err
         else
             wait = (1 + Math.random()) * 500
-            wait = ~~wait << @errors  # ~~ is to coerce to an int
+            wait = ~~wait << (@errors * 2)   # ~~ is to coerce to an int
             setTimeout retry, wait

     # Take one change from the changes feed and give it to merge


chore(build.conf): change *git commit* branch to `master`

--- config/build.conf.coffee
+++ config/build.conf.coffee
@@ -1,7 +1,7 @@
 # This file/module contains all configuration for the build process.

 module.exports =
-  # TODO: Rewrite comment to reflect changes in the code. -- JR
+  # TODO: Rewrite comments to reflect changes in the code. -- JR
   # The `build_dir` folder is where our projects are compiled during
   # development and the `compile_dir` folder is where our app resides once it's
   # completely built.
@@ -84,7 +84,7 @@
       """

     git:
-      branch: 'origin'
+      branch: 'master'
       tagName: 'v%VERSION%'
       tagMessage: 'Version %VERSION%'


Trim package names and remove empty ones

--- src/install.coffee
+++ src/install.coffee
@@ -337,7 +337,9 @@
         @installPackage({name, version}, options, callback)

     commands = []
-    packageNames = _.uniq(options.argv._)
+    packageNames = options.argv._.map (packageName) -> packageName.trim()
+    packageNames = _.compact(packageNames)
+    packageNames = _.uniq(packageNames)
     packageNames.push('.') if packageNames.length is 0
     packageNames.forEach (packageName) ->
       commands.push (callback) -> installPackage(packageName, callback)


on launch, reopen files.

on launch, reopen files.

--- plugins/project/project.coffee
+++ plugins/project/project.coffee
@@ -19,9 +19,32 @@

   initialize: ->
     @reload(File.workingDirectory())
+    @editor = activeWindow.document
+
+    @editor.ace.on 'open', ({filename}) =>
+      if File.isDirectory filename
+        @reload filename
+      else
+        openedPaths = @get 'openPaths', []
+        if not openedPaths.indexOf filename
+          openedPaths.push filename
+          @set 'openPaths', openedPaths

-    activeWindow.document.ace.on 'open', ({filename}) =>
-      @reload filename if File.isDirectory filename
+    @editor.ace.on 'close', ({filename}) =>
+      if File.isFile filename
+        openedPaths = _.without @get('openPaths', []), filename
+        @set 'openPaths', openedPaths
+
+    @editor.ace.on 'loaded', =>
+      # Reopen files (remove ones that no longer exist)
+      openedPaths = @get 'openPaths', []
+      for path in openedPaths
+        if File.exists path
+          @editor.open path
+        else
+         openedPaths = _.without(openedPaths, path)
+      @set "openedPaths", openedPaths
+

     $('#project li').live 'click', (event) =>
       $('#project .active').removeClass 'active'
@@ -57,7 +80,9 @@

   createList: (dir) ->
     paths = File.list dir
-    visibleDirs = @get('visibleDirs') or {}
+
+    # BUG: need to clear visibleDirs that don't exist anymore
+    visibleDirs = @get('visibleDirs', {})
     list = $('<ul>')
     for path in paths
       filename = path.replace(dir, "").substring 1


improve validateVesselInput method

improve validateVesselInput method

--- imports/api/vessels/methods.coffee
+++ imports/api/vessels/methods.coffee
@@ -153,23 +153,23 @@
 		if typeof args.callsign == 'string' && args.callsign.length > 0
 			vessel = Vessels.findOne(callsign: args.callsign)

-			if (args._id? && args.callsign != vessel.callsign) || (!args._id? && vessel?)
+			if vessel? && ((args._id? && args._id != vessel._id) || !args._id?)
 				throw new Meteor.Error 'validation-error', 'callsignUnique'

 		if typeof args.eni == 'string' && args.eni.length > 0
 			vessel = Vessels.findOne(eni: args.eni)

-			if (args._id? && args.eni != vessel.eni) || (!args._id? && vessel?)
+			if vessel? && ((args._id? && args._id != vessel._id) || !args._id?)
 				throw new Meteor.Error 'validation-error', 'eniUnique'

 		if typeof args.imo == 'string' && args.imo.length > 0
 			vessel = Vessels.findOne(imo: args.imo)

-			if (args._id? && args.imo != vessel.imo) || (!args._id? && vessel?)
+			if vessel? && ((args._id? && args._id != vessel._id) || !args._id?)
 				throw new Meteor.Error 'validation-error', 'imoUnique'

 		if typeof args.mmsi == 'string' && args.mmsi.length > 0
 			vessel = Vessels.findOne(mmsi: args.mmsi)

-			if (args._id? && args.mmsi != vessel.mmsi) || (!args._id? && vessel?)
+			if vessel? && ((args._id? && args._id != vessel._id) || !args._id?)
 				throw new Meteor.Error 'validation-error', 'mmsiUnique'


removed "spawn port" field from connections page

--- client/connect.coffee
+++ client/connect.coffee
@@ -54,7 +54,6 @@
     <fieldset id="spawnSection" style="display:none">
       <legend>Spawn an interpreter</legend>

-        Port <input id="spawn-port" class="text-field" value="#{DEFAULT_PORT}" size="5">
         Spawn

@@ -72,14 +71,13 @@
   $delete      = $ '#fav-delete'   ; $name        = $ '#fav-name'
   $list        = $ '#fav-list'     ; $save        = $ '#fav-save'
   $spawn       = $ '#spawn'        ; $cancel      = $ '#fav-cancel'
-  $spawnPort   = $ '#spawn-port'   ; $listen      = $ '#listen'
-  $spawnStatus = $ '#spawn-status' ; $listenPort  = $ '#listen-port'
-  $about       = $ '#about'
+  $spawnStatus = $ '#spawn-status' ; $listen      = $ '#listen'
+  $about       = $ '#about'        ; $listenPort  = $ '#listen-port'
   $listenDialog = null

   enableSpawnAndListen = (b) ->
     $('#spawn, #listen').button if b then 'enable' else 'disable'
-    $('#spawn-port, #listen-port').attr 'disabled', !b
+    $('#listen-port').attr 'disabled', !b
     return

   $connect.add($about).add($new).add($delete).add($save).add($cancel).add($spawn).add($listen).button()
@@ -120,14 +118,7 @@
   $cancel.click ->
     x = parseFav $list.find(':selected').text(); $host.val x.host; $port.val x.port; $name.val x.name
     $save.add($cancel).button 'disable'; false
-  $spawn.click ->
-    port = +$spawnPort.val()
-    if !(0 < port < 0x10000)
-      $.alert 'Invalid port', 'Error', -> $spawnPort.focus(); return
-    else
-      enableSpawnAndListen false; $spawnStatus.text 'Spawning...'; D.socket.emit '*spawn', {port}
-    false
-  $spawnPort.on 'keydown', null, 'return', -> $spawn.click(); false
+  $spawn.click -> enableSpawnAndListen false; $spawnStatus.text 'Spawning...'; D.socket.emit '*spawn'; false
   $listen.click ->
     port = +$listenPort.val()
     if !(0 < port < 0x10000)
@@ -191,7 +182,7 @@
     setTimeout(
       ->
         if      o.listen  then (if o._[0] then $listenPort.val o._[0]); $listen.click()
-        else if o.spawn   then (if o._[0] then $spawnPort.val  o._[0]); $spawn.click()
+        else if o.spawn   then $spawn.click()
         else if o.connect then hp = parseFav o.connect; D.socket.emit '*connect', host: hp.host, port: hp.port or DEFAULT_PORT
         return
       100 # TODO: race condition?


Revert "Add menu"

Revert "Add menu"

This reverts commit 8e5f76ae5b94111859b4fe48b7a654a04bd69ad2.

--- menus/project-manager.cson
+++ menus/project-manager.cson
@@ -1,26 +1,27 @@
-'menu': [
-  {
-    'label': 'Packages'
-    'submenu': [
-      'label': 'Project Manager'
-      'submenu': [
-        {
-          'label': 'List Projects',
-          'command': 'project-manager:list-projects'
-        }
-        {
-          'label': 'Save Project',
-          'command': 'project-manager:save-project'
-        }
-        {
-          'label': 'Edit Projects',
-          'command': 'project-manager:edit-projects'
-        }
-        {
-          'label': 'Reload Projects Settings',
-          'command': 'project-manager:reload-project-settings'
-        }
-      ]
-    ]
-  }
-]
+# See https://atom.io/docs/latest/creating-a-package#menus for more details
+# 'menu': [
+#   {
+#     'label': 'Packages'
+#     'submenu': [
+#       'label': 'Project Manager'
+#       'submenu': [
+#         {
+#           'label': 'Toggle',
+#           'command': 'project-manager:toggle'
+#         }
+#         {
+#           'label': 'Save Project',
+#           'command': 'project-manager:save-project'
+#         }
+#         {
+#           'label': 'Edit Projects',
+#           'command': 'project-manager:edit-projects'
+#         }
+#         {
+#           'label': 'Reload Projects Settings',
+#           'command': 'project-manager:reload-project-settings'
+#         }
+#       ]
+#     ]
+#   }
+# ]


unit tests for locking timeouts

unit tests for locking timeouts

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

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

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


Cleaner import of errors

Cleaner import of errors

--- services/web/app/coffee/Features/LinkedFiles/ProjectOutputFileAgent.coffee
+++ services/web/app/coffee/Features/LinkedFiles/ProjectOutputFileAgent.coffee
@@ -5,7 +5,12 @@
 ClsiManager = require '../Compile/ClsiManager'
 ProjectFileAgent = require './ProjectFileAgent'
 _ = require "underscore"
-LinkedFilesErrors = require './LinkedFilesErrors'
+{
+	BadDataError,
+	AccessDeniedError,
+	BadEntityTypeError,
+	OutputFileFetchFailedError
+} = require './LinkedFilesErrors'
 LinkedFilesHandler = require './LinkedFilesHandler'
 logger = require 'logger-sharelatex'

@@ -15,7 +20,7 @@
 	_prepare: (project_id, linkedFileData, user_id, callback=(err, linkedFileData)->) ->
 		@_checkAuth project_id, linkedFileData, user_id, (err, allowed) =>
 			return callback(err) if err?
-			return callback(new LinkedFilesErrors.AccessDeniedError()) if !allowed
+			return callback(new AccessDeniedError()) if !allowed
 			@_decorateLinkedFileData linkedFileData, (err, newLinkedFileData) =>
 				return callback(err) if err?
 				if !@_validate(newLinkedFileData)
@@ -24,7 +29,7 @@

 	createLinkedFile: (project_id, linkedFileData, name, parent_folder_id, user_id, callback) ->
 		if !@_canCreate(linkedFileData)
-			return callback(new LinkedFilesErrors.AccessDeniedError())
+			return callback(new AccessDeniedError())
 		linkedFileData = @_sanitizeData(linkedFileData)
 		@_prepare project_id, linkedFileData, user_id, (err, linkedFileData) =>
 			return callback(err) if err?
@@ -44,7 +49,7 @@
 								return callback(err) if err?
 								callback(null, file._id) # Created
 					else
-						err = new LinkedFilesErrors.OutputFileFetchFailedError(
+						err = new OutputFileFetchFailedError(
 							"Output file fetch failed: #{linkedFileData.build_id}, #{linkedFileData.source_output_file_path}"
 						)
 						err.statusCode = response.statusCode
@@ -70,7 +75,7 @@
 								return callback(err) if err?
 								callback(null, file._id) # Created
 					else
-						err = new LinkedFilesErrors.OutputFileFetchFailedError(
+						err = new OutputFileFetchFailedError(
 							"Output file fetch failed: #{linkedFileData.build_id}, #{linkedFileData.source_output_file_path}"
 						)
 						err.statusCode = response.statusCode
@@ -101,7 +106,7 @@
 	_checkAuth: (project_id, data, current_user_id, callback = (err, allowed)->) ->
 		callback = _.once(callback)
 		if !@_validate(data)
-			return callback(new LinkedFilesErrors.BadDataError())
+			return callback(new BadDataError())
 		@_getSourceProject data, (err, project) ->
 			return callback(err) if err?
 			AuthorizationManager.canUserReadProject current_user_id,
@@ -138,13 +143,13 @@
 				(err, status, outputFiles) ->
 					return callback(err) if err?
 					if status != 'success'
-						return callback(new LinkedFilesErrors.OutputFileFetchFailedError())
+						return callback(new OutputFileFetchFailedError())
 					outputFile = _.find(
 						outputFiles,
 						(o) => o.path == source_output_file_path
 					)
 					if !outputFile?
-						return callback(new LinkedFilesErrors.OutputFileFetchFailedError())
+						return callback(new OutputFileFetchFailedError())
 					build_id = outputFile.build
 					ClsiManager.getOutputFileStream source_project_id,
 						user_id,


logging: allow plural messages

logging: allow plural messages

--- src/logging.coffee
+++ src/logging.coffee
@@ -15,8 +15,8 @@

 enableLogging = (name, emitter, logLevel = INFO) ->

-  createLogger = (category, style, fn) -> (message) ->
-    fn style "[#{ name }] #{ category }\t#{ message }"
+  createLogger = (category, style, fn) -> (messages...) ->
+    fn style "[#{ name }] #{ category }\t#{ messages.join ' ' }"

   if CRITICAL <= logLevel
     emitter.on 'critical', createLogger 'CRITICAL', color.cyan, error


Update _player.coffee

Update _player.coffee

--- coffee/_player.coffee
+++ coffee/_player.coffee
@@ -240,6 +240,8 @@
     playerContainer
       .find('.info .track-info .action i.pause')
       .show()
+
+    notify.NowPlaying(__currentTrack.artist, __currentTrack.title)

   @.on 'pause', ->
     playerContainer


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -103,7 +103,7 @@

   player.include Metered

-  player.meter 'heal'
+  player.meter 'health'
   # => {x: 50, y: 40}
   </pre></code>


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -103,7 +103,7 @@

   player.include Metered

-  player.meter 'health'
+  player.meter 'health',
   # =>
   </pre></code>


movable

movable

--- src/movable.coffee
+++ src/movable.coffee
@@ -4,32 +4,31 @@
 collisions so is probably best suited to particle effect like things.

-   player = GameObject
-     x: 0
-     y: 0
-     velocity: Point(0, 0)
-     acceleration: Point(1, 0)
-     maxSpeed: 2
+player = GameObject
+  x: 0
+  y: 0
+  velocity: Point(0, 0)
+  acceleration: Point(1, 0)
+  maxSpeed: 2

-   player.include(Movable)
+player.include(Movable)

-=> `velocity is {x: 0, y: 0} and position is {x: 0, y: 0}`
+# => `velocity is {x: 0, y: 0} and position is {x: 0, y: 0}`

-   player.update()
-=> `velocity is {x: 1, y: 0} and position is {x: 1, y: 0}`
+player.update()
+# => `velocity is {x: 1, y: 0} and position is {x: 1, y: 0}`

-   player.update()
-=> `velocity is {x: 2, y: 0} and position is {x: 3, y: 0}`
+player.update()
+# => `velocity is {x: 2, y: 0} and position is {x: 3, y: 0}`

-   # we've hit our maxSpeed so our velocity won't increase
-   player.update()
-=> `velocity is {x: 2, y: 0} and position is {x: 5, y: 0}`
+# we've hit our maxSpeed so our velocity won't increase
+player.update()
+# => `velocity is {x: 2, y: 0} and position is {x: 5, y: 0}`

 @name Movable
 @module
 @constructor
-
 @param {Object} I Instance variables
 @param {Core} self Reference to including object
 ###


avoid printing fake frames altogether

avoid printing fake frames altogether

--- src/natives.coffee
+++ src/natives.coffee
@@ -74,9 +74,9 @@
             # we don't want to include the stack frames that were created by
             # the construction of this exception
             cstack = rs.meta_stack()._cs.slice(1,-1)
-            for sf in cstack when sf.locals[0] isnt _this
+            for sf in cstack when not (sf.fake? or sf.locals[0] is _this)
               cls = sf.method.class_type
-              unless sf.fake? or _this.type.toClassString() is 'java/lang/NoClassDefFoundError'
+              unless _this.type.toClassString() is 'java/lang/NoClassDefFoundError'
                 attrs = rs.load_class(cls).attrs
                 source_file =
                   _.find(attrs, (attr) -> attr.constructor.name == 'SourceFile')?.name or 'unknown'


Admin: Stacks: Emit stack template update request.

--- client/admin/lib/views/stacks/onboarding/codesetupview.coffee
+++ client/admin/lib/views/stacks/onboarding/codesetupview.coffee
@@ -40,6 +40,7 @@
           serviceView.setClass  'selected'
           @selected?.unsetClass 'selected'
           @selected = if @selected is serviceView then null else serviceView
+          @emit 'StackTemplateNeedsToBeUpdated'


Client:Activity: Renamed browseChannels state to browseFollowedPublicChannels

--- client/app/lib/components/sidebarchannelssection/index.coffee
+++ client/app/lib/components/sidebarchannelssection/index.coffee
@@ -16,17 +16,17 @@

     super

-    @state = { browseChannels: no }
+    @state = { browseFollowedPublicChannels: no }

   onClose: ->

-    @setState browseChannels: no
+    @setState browseFollowedPublicChannels: no

-  showFollowedChannelsModal: ->
+  showFollowedPublicChannelsModal: ->

-    @setState browseChannels: yes
+    @setState browseFollowedPublicChannels: yes

   renderFollowedChannelsModal: ->


sidebar: section wrapper removed for less nesting

--- client/app/lib/components/sidebarmessagessection/index.coffee
+++ client/app/lib/components/sidebarmessagessection/index.coffee
@@ -24,18 +24,18 @@

   render: ->

-    <div>
-      <SidebarSection
-        title="Messages"
-        titleLink="/AllMessages"
-        secondaryLink="/NewMessage"
-        className="SidebarMessagesSection">
-        <SidebarList
-          previewCount={@props.previewCount}
-          itemComponent={SidebarMessagesListItem}
-          componentProp='SidebarMessagesListItem'
-          threads={@props.threads}
-          selectedId={@props.selectedId} />
-        {@renderMoreLink()}
-      </SidebarSection>
-    </div>
+    <SidebarSection
+      title="Messages"
+      titleLink="/AllMessages"
+      secondaryLink="/NewMessage"
+      className="SidebarMessagesSection">
+      <SidebarList
+        previewCount={@props.previewCount}
+        itemComponent={SidebarMessagesListItem}
+        componentProp='SidebarMessagesListItem'
+        threads={@props.threads}
+        selectedId={@props.selectedId} />
+      {@renderMoreLink()}
+    </SidebarSection>
+
+


Back to display == none for $.fn.isHidden()

--- src/jquery-extensions.coffee
+++ src/jquery-extensions.coffee
@@ -38,18 +38,9 @@
   !@isHidden()

 $.fn.isHidden = ->
-  # Implementation taken from jQuery's `:hidden` expression code:
-  # https://github.com/jquery/jquery/blob/master/src/css/hiddenVisibleSelectors.js
-  #
-  # We were using a pseudo selector: @is(':hidden'). But jQuery's pseudo
-  # selector code checks the element's webkitMatchesSelector, which is always
-  # false, and is really really really slow.
-
-  elem = this[0]
-
-  return null unless elem
-
-  elem.offsetWidth <= 0 and elem.offsetHeight <= 0
+  # We used to check @is(':hidden'). But this is much faster than the
+  # offsetWidth/offsetHeight check + all the pseudo selector mess in jquery.
+  @css('display') == 'none'

 $.fn.isDisabled = ->
   !!@attr('disabled')


Factor out animator option from tip with default option.

--- src/js/jquery.hlf.tip.coffee
+++ src/js/jquery.hlf.tip.coffee
@@ -106,13 +106,18 @@
         (classNames[key] = "#{pre}#{key}") for key in keys
         classNames.tip = 'js-tip'
         classNames
+      # - `animator` by default uses the stock jQuery animation system. If you
+      #   want a more performant system, like GreenSock or Velocity, replace
+      #   this with a custom implementation.
+      animator:
+        show: ($el, options) -> $el.stop().fadeIn options
+        hide: ($el, options) -> $el.stop().fadeOut options
       # - `animations` are very configurable. Individual animations can be
       #   customized and will default to the base animation settings as needed.
       animations:
         base:
           delay: 0
           duration: 200
-          easing: 'ease-in-out'
           enabled: yes
         show:
           delay: 200
@@ -120,6 +125,7 @@
           delay: 200
         resize:
           delay: 300
+          easing: 'ease-in-out'

   hlf.tip.snap =
     debug: off
@@ -338,12 +344,17 @@
       # - Our `wake` subroutine runs the timed-out logic, which includes the fade
       #   transition. Ensure other tips disappear during the transition.
       wake = (duration) =>
-        duration ?= @animations.show.duration
-        updateBeforeWake()
-        @$tip.stop().fadeIn duration, =>
-          @_setState 'awake', event # Hook in custom logic.
+        updateBeforeWake() # Hook in custom logic.
+        options = _.defaults { duration }, @animations.show
+        options.done = =>
+          @_setState 'awake', event
           deferred.resolve()
-        @$tip.siblings(@classNames.tip).stop().fadeOut duration
+        options.fail = -> deferred.reject()
+        @animator.show @$tip, options
+        @$tip.siblings(@classNames.tip).each (idx, el) =>
+          options = _.defaults { duration }, @animation.hide
+          @animator.hide $el, options
+
       # - Wake up depending on current state. If we are in the middle of
       #   sleeping, stop sleeping by updating `_sleepCountdown` and wake up
       #   sooner.
@@ -374,9 +385,12 @@
       @_setState 'sleeping'
       @_sleepCountdown = setTimeout =>
         @onHide() # Hook in custom logic.
-        @$tip.stop().fadeOut @animations.hide.duration, =>
+        options = _.clone @animations.hide
+        options.done = =>
           @_setState 'asleep'
           deferred.resolve()
+        options.fail = -> deferred.reject()
+        @animator.hide @$tip, options
       , @animations.hide.delay

       promise