Add specs for updating lineHeightInPixels when changing font size/family

--- spec/editor-component-spec.coffee
+++ spec/editor-component-spec.coffee
@@ -94,6 +94,27 @@
       initialLineHeightInPixels = editor.getLineHeightInPixels()
       component.setLineHeight(2)

+      newLineHeightInPixels = editor.getLineHeightInPixels()
+      expect(newLineHeightInPixels).not.toBe initialLineHeightInPixels
+      expect(component.lineNodeForScreenRow(1).offsetTop).toBe 1 * newLineHeightInPixels
+
+    it "updates the top position of lines when the font size changes", ->
+      initialLineHeightInPixels = editor.getLineHeightInPixels()
+      component.setFontSize(10)
+
+      newLineHeightInPixels = editor.getLineHeightInPixels()
+      expect(newLineHeightInPixels).not.toBe initialLineHeightInPixels
+      expect(component.lineNodeForScreenRow(1).offsetTop).toBe 1 * newLineHeightInPixels
+
+    it "updates the top position of lines when the font family changes", ->
+      # Can't find a font that changes the line height, but we think one might exist
+      linesComponent = component.refs.scrollView.refs.lines
+      spyOn(linesComponent, 'measureLineHeightInPixelsAndCharWidth').andCallFake -> editor.setLineHeightInPixels(10)
+
+      initialLineHeightInPixels = editor.getLineHeightInPixels()
+      component.setFontFamily('sans-serif')
+
+      expect(linesComponent.measureLineHeightInPixelsAndCharWidth).toHaveBeenCalled()
       newLineHeightInPixels = editor.getLineHeightInPixels()
       expect(newLineHeightInPixels).not.toBe initialLineHeightInPixels
       expect(component.lineNodeForScreenRow(1).offsetTop).toBe 1 * newLineHeightInPixels


Fix empty mini pie charts

Fix empty mini pie charts

--- lineman/app/js/controllers/mini_proposal_pie_chart_controller.coffee
+++ lineman/app/js/controllers/mini_proposal_pie_chart_controller.coffee
@@ -4,7 +4,8 @@
     { value : 0, color : "#90D490" },
     { value : 0, color : "#F0BB67" },
     { value : 0, color : "#D49090" },
-    { value : 0, color : "#dd0000"}
+    { value : 0, color : "#dd0000" },
+    { value : 0, color : "#cccccc" }
   ]

   $scope.pieChartOptions =
@@ -15,11 +16,12 @@
   refreshPieChartData = ->
     return unless $scope.proposal
     counts = $scope.proposal.voteCounts
-    # yeah - this is done to preseve the view binding on the pieChartData
+    hasAnyVotes = counts.yes + counts.abstain + counts.no + counts.block > 0
     $scope.pieChartData[0].value = counts.yes
     $scope.pieChartData[1].value = counts.abstain
     $scope.pieChartData[2].value = counts.no
     $scope.pieChartData[3].value = counts.block
+    $scope.pieChartData[4].value = if hasAnyVotes then 0 else 1

   $scope.$watch 'proposal.voteCounts', ->
     refreshPieChartData()


set default pageSize to 100 for users

set default pageSize to 100 for users

--- app/assets/javascripts/models/user.js.coffee
+++ app/assets/javascripts/models/user.js.coffee
@@ -60,6 +60,8 @@
   class exports.UserList extends Stark.Collection
     model: exports.User
     @key: "users"
+    state:
+      pageSize: 100
     url: "/rest/users"

     comparator: (user) ->


don't log missing files as warnings, but do report file access errors

--- app/coffee/StaticServerForbidSymlinks.coffee
+++ app/coffee/StaticServerForbidSymlinks.coffee
@@ -29,10 +29,10 @@
 		# check that the requested path is not a symlink
 		fs.realpath requestedFsPath, (err, realFsPath)->
 			if err?
-				logger.warn err:err, requestedFsPath:requestedFsPath, realFsPath:realFsPath, path: req.params[0], project_id: req.params.project_id, "error checking file access"
 				if err.code == 'ENOENT'
 					return res.sendStatus(404)
 				else
+					logger.error err:err, requestedFsPath:requestedFsPath, realFsPath:realFsPath, path: req.params[0], project_id: req.params.project_id, "error checking file access"
 					return res.sendStatus(500)
 			else if requestedFsPath != realFsPath
 				logger.warn requestedFsPath:requestedFsPath, realFsPath:realFsPath, path: req.params[0], project_id: req.params.project_id, "trying to access a different file (symlink), aborting"


Add TOO DAMN HIGH and Professor Farnsworth

--- src/scripts/meme_generator.coffee
+++ src/scripts/meme_generator.coffee
@@ -10,6 +10,10 @@
 # <text> (SUCCESS|NAILED IT) - Generates success kid with the top caption of <text>
 #
 # <text> ALL the <things>    - Generates ALL THE THINGS
+#
+# <text> TOO DAMN <high> - Generates THE RENT IS TOO DAMN HIGH guy
+#
+# Good news everyone! <news> - Generates Professor Farnsworth

 module.exports = (robot) ->
   robot.respond /Y U NO (.+)/i, (msg) ->
@@ -32,6 +36,14 @@

   robot.respond /(.*) (ALL the .*)/, (msg) ->
     memeGenerator msg, 6013, 1121885, msg.match[1], msg.match[2], (url) ->
+      msg.send url
+
+  robot.respond /(.*) (\w+\sTOO DAMN .*)/i, (msg) ->
+    memeGenerator msg, 998, 203665, msg.match[1], msg.match[2], (url) ->
+      msg.send url
+
+  robot.respond /^(GOOD NEWS EVERYONE[,.!]?) (.*)/i, (msg) ->
+    memeGenerator msg, 1591, 112464, msg.match[1], msg.match[2], (url) ->
       msg.send url

 memeGenerator = (msg, generatorID, imageID, text0, text1, callback) ->


Don't append undefined item

Don't append undefined item

--- resources/assets/coffee/react/_components/comments-manager.coffee
+++ resources/assets/coffee/react/_components/comments-manager.coffee
@@ -35,11 +35,11 @@
         osu.parseJson("json-comments-#{@props.componentProps?.commentableType}-#{@props.componentProps?.commentableId}")

       @state =
-        comments: comments ? commentBundle.comments
+        comments: comments ? commentBundle.comments ? []
         userVotes: commentBundle.user_votes
-        users: users ? commentBundle.users
+        users: users ? commentBundle.users ? []
         topLevelCount: commentBundle.top_level_count
-        commentableMeta: commentBundle.commentable_meta
+        commentableMeta: commentBundle.commentable_meta ? []

   componentDidMount: =>
@@ -76,10 +76,14 @@

   update: (_event, {comment}) =>
-    @setState
+    newState =
       comments: osu.updateCollection @state.comments, [comment]
       users: osu.updateCollection @state.users, [comment.user, comment.editor]
-      commentableMeta: osu.updateCollection @state.commentableMeta, [comment.commentable_meta]
+
+    if comment.commentable_meta?
+      newState.commentableMeta = osu.updateCollection @state.commentableMeta, [comment.commentable_meta]
+
+    @setState newState

   addVote: (_event, {id}) =>


NEW: showPartialDrawAreas query-string option for debugging IMPROVED PERFORMANCE: clipping + partial-draw updates no longer creates a function for every draw

--- source/Art/Engine/Core/CanvasElement.coffee
+++ source/Art/Engine/Core/CanvasElement.coffee
@@ -23,15 +23,19 @@
   timeout
   durationString
   timeStampToPerformanceSecond
-  first, Browser
-  createWithPostCreate
+  first
   wordsArray
   select
   merge
   objectDiff
   isPlainObject
   clone
-} = Foundation
+  getEnv
+} = require 'art-standard-lib'
+{createWithPostCreate} = require 'art-class-system'
+{Browser} = require 'art-foundation'
+
+{showPartialDrawAreas} = getEnv()

 {isMobileBrowser} = Browser
 HtmlCanvas = Browser.DomElementFactories.Canvas
@@ -567,13 +571,15 @@
     if @canvasBitmap
       if config.partialRedrawEnabled && @_dirtyDrawAreas
         for dirtyDrawArea in @_dirtyDrawAreas
-          @canvasBitmap.clippedTo dirtyDrawArea.mul(@_devicePixelsPerPoint), =>
-            super @canvasBitmap, @elementToParentMatrix
+          lastClippingInfo = @canvasBitmap.openClipping dirtyDrawArea.mul @_devicePixelsPerPoint
+          super @canvasBitmap, @elementToParentMatrix
+          @canvasBitmap.closeClipping lastClippingInfo
       else
         super @canvasBitmap, @elementToParentMatrix

-    # for dirtyDrawArea in @_dirtyDrawAreas || [@drawArea]
-    #   @canvasBitmap?.drawBorder null, dirtyDrawArea, color: "red"
+    if showPartialDrawAreas
+      for dirtyDrawArea in @_dirtyDrawAreas || [@drawArea]
+        @canvasBitmap?.drawBorder null, (dirtyDrawArea.mul @_devicePixelsPerPoint), color: "red"

     frameEndTime = currentSecond()
     @engineStat.add "drawTimeMS", (frameEndTime - frameStartTime) * 1000 | 0


pass headers through when proxying request to allow for byte-range/etag handling

--- services/web/app/coffee/Features/Compile/CompileController.coffee
+++ services/web/app/coffee/Features/Compile/CompileController.coffee
@@ -78,7 +78,7 @@
 			url = "#{compilerUrl}#{url}"
 			logger.log url: url, "proxying to CLSI"
 			oneMinute = 60 * 1000
-			proxy = request(url: url, method: req.method, timeout: oneMinute)
+			proxy = request(url: url, method: req.method, timeout: oneMinute, headers: req.headers)
 			proxy.pipe(res)
 			proxy.on "error", (error) ->
 				logger.warn err: error, url: url, "CLSI proxy error"


Increase autocompile rollout to 60%

Increase autocompile rollout to 60%

--- services/web/app/coffee/Features/Project/ProjectController.coffee
+++ services/web/app/coffee/Features/Project/ProjectController.coffee
@@ -259,7 +259,7 @@
 				timestamp = parseInt(user_id.toString().substring(0, 8), 16)
 				counter = parseInt(user_id.toString().substring(18, 24), 16)

-				rolloutPercentage = 40 # Percentage of users to roll out to
+				rolloutPercentage = 60 # Percentage of users to roll out to
 				if counter % 100 > rolloutPercentage
 					# Don't show if user is not part of roll out
 					return cb(null, { enabled: false, showOnboarding: false })


Support dismissing the dialog just with Enter.

--- services/web/public/coffee/ide/FeatureOnboardingController.coffee
+++ services/web/public/coffee/ide/FeatureOnboardingController.coffee
@@ -21,3 +21,15 @@
 		navToInnerStep2 = () ->
 			$scope.innerStep = 2
 			$scope.ui.leftMenuShown = true
+
+		handleKeypress = (e) ->
+			if e.keyCode == 13
+				if $scope.innerStep == 1
+					$scope.turnCodeCheckOn()
+				else
+					$scope.dismiss()
+
+		$(document).on "keypress", handleKeypress
+
+		$scope.$on "$destroy", () ->
+			$(document).off "keypress", handleKeypress


avoid using jquery in pdf scrollhandler to improve peformance

--- services/web/public/coffee/ide/pdfng/directives/pdfViewer.coffee
+++ services/web/public/coffee/ide/pdfng/directives/pdfViewer.coffee
@@ -245,15 +245,15 @@
 					scope.document.renderPages(pages)

 				getVisiblePages = () ->
-					top = element.offset().top
-					bottom = top + element.innerHeight();
+					top = element[0].offsetTop
+					bottom = top + element[0].clientHeight
 					isVisible = (pageElement) ->
-						pageTop = pageElement.offset().top
-						pageBottom = pageTop + pageElement.innerHeight()
+						pageTop = pageElement.offsetTop
+						pageBottom = pageTop + pageElement.clientHeight
 						return pageTop < bottom and pageBottom > top
 					visiblePages = []
 					for page in scope.pages
-						page.visible = visible = isVisible(page.element)
+						page.visible = visible = isVisible(page.element[0])
 						visiblePages.push page if visible
 					return visiblePages


added abs, columns to functional tests

added abs, columns to functional tests

--- test/functional/druidFunctional.mocha.coffee
+++ test/functional/druidFunctional.mocha.coffee
@@ -43,6 +43,9 @@
             { name: 'anonymous', type: 'BOOLEAN' }
             { name: 'added', type: 'NUMBER' }
             { name: 'count', type: 'NUMBER' }
+            { name: 'commentLength', type: 'NUMBER' }
+            { name: 'metroCode', type: 'STRING' }
+            { name: 'cityName', type: 'STRING' }
             { name: 'user_unique', special: 'unique' }
           ]
           filter: $('time').in(TimeRange.fromJS({
@@ -590,6 +593,33 @@
               {
                 "Count": 22140
                 "Channel": "French"
+              }
+            ]
+          }
+        ])
+        testComplete()
+      ).done()
+
+    it "works with absolute value split", (testComplete) ->
+      ex = ply()
+      .apply('AbsSplit',
+        $('wiki').split($('commentLength').absolute(), 'AbsCommentLength')
+        .sort('$AbsCommentLength', 'descending')
+        .limit(3)
+      )
+
+      basicExecutor(ex).then((result) ->
+        expect(result.toJS()).to.deep.equal([
+          {
+            "AbsSplit": [
+              {
+                "AbsCommentLength": 1
+              }
+              {
+                "AbsCommentLength": 10
+              }
+              {
+                "AbsCommentLength": 100
               }
             ]
           }


Batch download of documents using ajax

Batch download of documents using ajax

--- app/assets/javascripts/species/views/documents/batch_download_component.js.coffee
+++ app/assets/javascripts/species/views/documents/batch_download_component.js.coffee
@@ -8,6 +8,21 @@
     documentIds = checked.map( ->
       $(@).closest('tr').data('document-id')
     ).toArray()
-    @.$().prop('href', '/api/v1/documents/download_zip?ids=' +
-      documentIds.join()
+    url = "/api/v1/documents/download_zip?ids=#{documentIds.join()}"
+    $.ajax({
+      type: 'GET'
+      dataType: 'json'
+      url: "/api/v1/documents/download_zip"
+      data: { ids: documentIds.join() }
+    }).done((data) =>
+      debugger
+      for doc in data.documents
+        ga('send', {
+          hitType: 'event',
+          eventCategory: "Downloads: #{doc.event_type}",
+          eventAction: doc.event_name,
+          label: doc.document_type,
+          value: doc.id
+        })
     )
+    window.location = url


Install eslint support

Install eslint support

--- atom/packages.cson
+++ atom/packages.cson
@@ -4,6 +4,8 @@
   "git-diff-popup"
   "git-plus"
   "language-javascript-jsx"
+  "linter"
+  "linter-eslint"
   "markdown-scroll-sync"
   "merge-conflicts"
   "package-sync"


Add @media CSS snippet (with @)

Add @media CSS snippet (with @)

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -693,12 +693,15 @@
     'prefix': 'endwhile'
     'body': '<?php endwhile; ?>$1'

-# CSS, Sass, and Less

 '.source.css':

-  '@media':
+  '@media (w/o @)':
     'prefix': 'media'
+    'body': '@media $1 {\n\t$2\n}'
+
+  '@media (w/ @)':
+    'prefix': '@media'
     'body': '@media $1 {\n\t$2\n}'

 # Sass


remove debug statement

remove debug statement

--- app/assets/javascripts/darkswarm/directives/logo_fallback.js.coffee
+++ app/assets/javascripts/darkswarm/directives/logo_fallback.js.coffee
@@ -1,7 +1,6 @@
 Darkswarm.directive "logoFallback", () ->
   restrict: "A"
   link: (scope, elm, attr)->
-    console.log elm
     elm.bind('error', ->
       elm.replaceWith("<i class='ofn-i_059-producer'></i>")
     )


Minor: typo

Minor: typo

--- src/resources/forms/orbeon/builder/resources/section-grid-repeat/section-editor.coffee
+++ src/resources/forms/orbeon/builder/resources/section-grid-repeat/section-editor.coffee
@@ -124,7 +124,7 @@
                 f$.focus labelInput

         # Update highlight of section title, as a hint users can click to edit
-        updateHightlight = (updateClass, clickInterceptor) ->
+        updateHighlight = (updateClass, clickInterceptor) ->
             offset = Builder.adjustedOffset clickInterceptor
             section = Builder.findInCache sectionsCache, offset.top
             sectionTitle = f$.find '.fr-section-title:first', section.el
@@ -151,10 +151,10 @@
                     f$.append container, $ '.fb-main'
                     container.on 'click', ({target}) -> showLabelEditor $ target
                     container.on 'mouseover', ({target}) ->
-                        updateHightlight f$.addClass, $ target
+                        updateHighlight f$.addClass, $ target
                         showClickHintIfTitleEmpty $ target
                     container.on 'mouseout', ({target}) ->
-                        updateHightlight f$.removeClass, $ target
+                        updateHighlight f$.removeClass, $ target
                         f$.text '', $ target
                     labelClickInterceptors.push container
                 # Hide interceptors we don't need


fixed web sole tang flop

fixed web sole tang flop

--- urb/zod/pub/sole/src/main.coffee
+++ urb/zod/pub/sole/src/main.coffee
@@ -82,7 +82,7 @@
     mapr = matr.props
     switch Object.keys(ruh)[0]
       when 'txt' then print ruh.txt
-      when 'tan' then ruh.tan.split("\n").reverse().map print
+      when 'tan' then ruh.tan.split("\n").map print
       when 'pro' then updPrompt app, ruh.pro.cad
       when 'hop' then update cursor: ruh.hop; bell() # XX buffer.transpose?
       when 'blk' then console.log "Stub #{str ruh}"


createTab: activate the first tab when opening a list of URLs

--- background_scripts/main.coffee
+++ background_scripts/main.coffee
@@ -130,7 +130,7 @@
     tabConfig.openerTabId = request.tab.id if canUseOpenerTabId

     chrome.tabs.create tabConfig, (tab) ->
-      callback extend request, {tab, tabId: tab.id}
+      callback extend request, {tab, tabId: tab.id, position: "", active: false}

   # Opens request.url in new window and switches to it.
   openUrlInNewWindow: (request, callback = (->)) ->
@@ -217,10 +217,10 @@
       chrome.windows.create windowConfig, -> callback request
     else
       urls = request.urls[..].reverse()
-      position = request.registryEntry.options.position
+      request.position ?= request.registryEntry.options.position
       do openNextUrl = (request) ->
         if 0 < urls.length
-          TabOperations.openUrlInNewTab (extend request, {url: urls.pop(), position}), openNextUrl
+          TabOperations.openUrlInNewTab (extend request, {url: urls.pop()}), openNextUrl
         else
           callback request
   duplicateTab: mkRepeatCommand (request, callback) ->


Add spec for application locale.

Add spec for application locale.

--- spec/chromium-spec.coffee
+++ spec/chromium-spec.coffee
@@ -26,6 +26,10 @@
       navigator.webkitGetUserMedia audio: true, video: false,
         -> done()
         -> done()
+
+  describe 'navigator.language', ->
+    it 'should not be empty', ->
+      assert.notEqual navigator.language, ''

   describe 'window.open', ->
     it 'returns a BrowserWindow object', ->


fix bug

fix bug

--- scripts/order.coffee
+++ scripts/order.coffee
@@ -48,5 +48,3 @@
       item = msg.match[1].trim()
       user = msg.message.user.name or "路人"
       orders.push new Order(item, user)
-    else
-      msg.send "還沒開始點餐"


activity: skip invoking activity hiding procedures if respective view doesn't exist

--- client/Social/Activity/activitylistcontroller.coffee
+++ client/Social/Activity/activitylistcontroller.coffee
@@ -34,17 +34,17 @@
       subject  = @prepareSubject post
       instance = @addItem subject, 0

+      return  unless instance
+
       if bugTag and not @isMine subject
         instance.hide()
         @hiddenItems.push instance

-      if @activityHeader?.liveUpdateToggle.getState().title isnt 'live' and\
-         not @isMine subject
-
+      liveUpdate = @activityHeader?.liveUpdateToggle.getState().title is 'live'
+      if not liveUpdate and not @isMine subject
         instance.hide()
         @hiddenItems.push instance
         @activityHeader.newActivityArrived() unless bugTag
-

   prepareSubject:(post)->
     {subject} = post


Client: Activity: Added create public/private channe success vent listeners

--- client/activity/lib/flux/stores/channelsstore.coffee
+++ client/activity/lib/flux/stores/channelsstore.coffee
@@ -1,7 +1,8 @@
-KodingFluxStore = require 'app/flux/store'
-actions         = require '../actions/actiontypes'
-toImmutable     = require 'app/util/toImmutable'
-immutable       = require 'immutable'
+KodingFluxStore      = require 'app/flux/store'
+actions              = require '../actions/actiontypes'
+toImmutable          = require 'app/util/toImmutable'
+immutable            = require 'immutable'
+createChannelActions = require 'activity/flux/createchannel/actions/actiontypes'

 module.exports = class ChannelsStore extends KodingFluxStore

@@ -28,6 +29,8 @@
     @on actions.ADD_PARTICIPANTS_TO_CHANNEL_BEGIN, @handleAddParticipantsToChannelBegin
     @on actions.ADD_PARTICIPANTS_TO_CHANNEL_FAIL, @handleAddParticipantsToChannelFail

+    @on createChannelActions.CREATE_PRIVATE_CHANNEL_SUCCESS, @handleLoadChannelSuccess
+    @on createChannelActions.CREATE_PUBLIC_CHANNEL_SUCCESS, @handleLoadChannelSuccess

   handleLoadChannelSuccess: (channels, { channel }) ->


teams: stacks: some copy changes

teams: stacks: some copy changes

--- client/admin/lib/views/stacks/definestackview.coffee
+++ client/admin/lib/views/stacks/definestackview.coffee
@@ -271,14 +271,15 @@
       if not currentGroup.stackTemplates?.length
         @handleSetDefaultTemplate completed = no
         @outputView.addAndWarn "
-          Stack template saved and set for your team members.
-          You can now close this window or continue working on
-          your awesome stack template.
+          Your stack script has been successfully saved and applied
+          to all your team members. You can now close this window
+          and continue working with your stack.
         "

       else
-        @outputView.add "You can now close this window, or set this
-                         template as default for your team members."
+        @outputView.add "You can now apply your stack changes to all your
+                         team members or close this window and continue to
+                         work on your stack script."

         @setAsDefaultButton.show()
         @saveButton.hide()


readding openGroup which was removed during a merge

--- client/app/MainApp/lazy/staticgroupcontroller.coffee
+++ client/app/MainApp/lazy/staticgroupcontroller.coffee
@@ -292,7 +292,14 @@
         delegate : @
       ,@getData()

-    @lazyDomController.hideLandingPage()
+    @openGroup()
+
+  openGroup:->
+    router = KD.getSingleton 'router'
+    if router.getCurrentPath() is @groupEntryPoint
+      @lazyDomController.openPath "/#{@groupEntryPoint}/Activity"
+    else
+      @lazyDomController.hideLandingPage()

   decorateGuestStatus:->


GEN-2356 Enabled advanced options to solo product.

--- client/app/lib/providers/machinesettingsmodal.coffee
+++ client/app/lib/providers/machinesettingsmodal.coffee
@@ -80,7 +80,6 @@
     disabledTabsForTeams        = [ 'Domains', 'Advanced', 'Snapshots' ]
     disabledTabsForProviders    =
       managed                   : [ 'Specs', 'Domains', 'Snapshots' ]
-      softlayer                 : [ 'Snapshots' ]

     if isSoloProductLite()
       for item in hiddenTabsForSolo
@@ -90,12 +89,14 @@

       subView    = new item.viewClass item.viewOptions, machine
       isDisabled = not isMachineRunning and disabledTabsForNotRunningVM.indexOf(item.title) > -1
-      managedAndNotKoding = not isKoding() and machine.isManaged()
+      isDisabled = disabledTabsForTeams.indexOf(item.title) > -1  unless isKoding()

-      if disabledTabs = disabledTabsForProviders[machine.provider] or managedAndNotKoding
-        isDisabled = item.title in disabledTabs
-      else
-        isDisabled = item.title in disabledTabsForTeams
+      if machine.isManaged()
+        disabledTabs = disabledTabsForProviders[machine.provider]
+        isDisabled = disabledTabs.indexOf(item.title) > -1
+
+        # 'Advanced' tab should enable for managed VMs
+        isDisabled = no  if item.title is 'Advanced'

       @tabView.addPane pane = new KDTabPaneView
         name     : item.title


MachineSettingsModal: Added Shared VM section.

--- client/app/lib/providers/machinesettingsmodal.coffee
+++ client/app/lib/providers/machinesettingsmodal.coffee
@@ -36,6 +36,11 @@
     title       : 'Disk Usage'
     viewClass   : KDView
     viewOptions : partial: 'Disk Usage'
+  }
+  {
+    title       : 'Shared VM'
+    viewClass   : KDView
+    viewOptions : partial : 'Shared VM'
   }
 ]


Fixed item staying selected while learning

--- client/app/views/pages/card_learn_choice.tmpl.coffee
+++ client/app/views/pages/card_learn_choice.tmpl.coffee
@@ -1,7 +1,5 @@
 {div, ul, li, input, label} = require('../../modules/tags')
 c = require('../../modules/content').get
-
-# TODO-1 why is this preselecting an option?

 module.exports = (data, mode) ->
     {options} = data
@@ -30,6 +28,9 @@
                     value: option
                     id: i
                     disabled: disabled
+                    key: "#{data.id}-#{option}"
+                    # The key ensures the input doesn't stay selected
+                    # when changing questions
                 })
                 label({
                     htmlFor: i


Route: fix infinite redirect loop

Route: fix infinite redirect loop

--- client/landing/Intro/views/registerinlineform.coffee
+++ client/landing/Intro/views/registerinlineform.coffee
@@ -148,7 +148,7 @@
           duration  : 2000

         firstRoute = KD.getSingleton("router").visitedRoutes.first
-        if firstRoute and /^\/(?:Reset|Register|Confirm)\//.test firstRoute
+        if firstRoute and /^\/(?:Reset|Register|Confirm|R)\//.test firstRoute
           firstRoute = "/Activity"

         {entryPoint} = KD.config


utils: getProfile fetcher added

utils: getProfile fetcher added

--- client/landing/site.landing/coffee/core/utils.coffee
+++ client/landing/site.landing/coffee/core/utils.coffee
@@ -237,6 +237,15 @@
       error       : callbacks.error

+  getProfile: (email, callbacks) ->
+
+    $.ajax
+      url         : "/-/profile/#{email}"
+      type        : 'GET'
+      success     : callbacks.success
+      error       : callbacks.error
+
+
   joinTeam: (callbacks = {}) ->

     formData = createFormData()


Assign analytics module into utilities namespace

--- client/landing/site.landing/coffee/core/utils.coffee
+++ client/landing/site.landing/coffee/core/utils.coffee
@@ -8,6 +8,7 @@
         delete window.localStorage[kite]

+  analytics: require './analytics'

   getReferrer: ->


Fix team page typo

Fix team page typo

--- lib/en-us.coffee
+++ lib/en-us.coffee
@@ -139,7 +139,7 @@
     zooniverse: '''The Zooniverse is home to the internet’s largest, most popular and most successful citizen science projects. The Zooniverse and the suite of projects it contains is produced, maintained and developed by the Citizen Science Alliance. The member institutions of the CSA work with many academic and other partners around the world to produce projects that use the efforts and ability of volunteers to help scientists and researchers.'''
     hart: '''Tom is a penguinologist who runs the Penguin Lifelines project at Oxford University. When he is not making up words, he is researching the threats to penguins and how to mitigate them. He has two main interests: population genetics to study the population structure and movement in relation to climate change and using time-lapse cameras to study the timing of breeding. Much like a supercomputer, he gets smarter in colder temperatures. As a consequence, he can often be found thinking in the Southern Ocean.'''
     black: '''Caitlin is a DPhil student at the University of Oxford, studying how penguins adapt to environmental changes. Her research focuses on the plasticity of behaviors both during the summer breeding season and dark winter months. When she is not sorting through thousands of images or tramping through knee-deep snow in Antarctica, she enjoys cooking and listening to public radio.'''
-    zisserman: '''Andrew Zisserman is the Professor of Computer Vision Engineering at the Department of Engineering Science, University of Oxford, where he heads the Visual Geometry Group (www.robots.ox.ac.uk:/~vgg/). He researches visual recognition for images and videos.'''
+    zisserman: '''Andrew Zisserman is the Professor of Computer Vision Engineering at the Department of Engineering Science, University of Oxford, where he heads the Visual Geometry Group (www.robots.ox.ac.uk/~vgg/). He researches visual recognition for images and videos.'''
     aytar: '''Yusuf is a Post-Doctoral Research Assistant at the University of Oxford, studying computer vision and machine learning. His research focuses on teaching computers how to understand images and detect objects (including penguins) through machine learning. He enjoys playing football, basketball, and travelling.'''
     southwell: '''Colin is a Principal Research Scientist in the Australian Antarctic Division. His primary research interests are in providing scientific input to sustainable management of populations and ecosystems and the development of cost-effective methods for large-scale monitoring of seabird and seal populations in the Southern Ocean.'''
     emerson: '''Louise is a Senior Research Scientist at the Australian Antarctic Division. Her primary research interests are in Antarctic seabird ecology and conservation. Her research focuses on revealing the drivers of seabird population dynamics, identifying potential threats to seabirds and accounting for the birds needs in relation to fisheries management approaches. She is also passionately interested in educating the next generation of scientists and is actively involved in talking about her work in the classroom.'''


fix 中文 bugs

fix 中文 bugs

--- lib/event.coffee
+++ lib/event.coffee
@@ -7,7 +7,7 @@

     constructor: (@redis, @name) ->
         throw new Error("Missing redis connection") if not redis?
-        throw new Error('Invalid event name ' + @name) if not Event::name_format.test @name
+        # throw new Error('Invalid event name ' + @name) if not Event::name_format.test @name
         @key = "event:#{@name}"
         @badge = 0


Do not fail if credentials are not given

--- test/run-integration-tests.coffee
+++ test/run-integration-tests.coffee
@@ -5,9 +5,12 @@
 else
   describe.skip 'Redis Test Suite', -> it 'Dummy it'

-if ironCreds = require '../iron.json'
-  process.env.IRONMQ_TOKEN      = ironCreds.token
-  process.env.IRONMQ_PROJECT_ID = ironCreds.project_id
+try
+  if ironCreds = require '../iron.json'
+    process.env.IRONMQ_TOKEN      = ironCreds.token
+    process.env.IRONMQ_PROJECT_ID = ironCreds.project_id
+catch err
+  # not loading credentials

 if process.env.IRONMQ_PROJECT_ID and process.env.IRONMQ_TOKEN
   require './integration/queues/ironmq'


:white_check_mark: Spec for restore focus when clicked button

--- spec/tool-bar-spec.coffee
+++ spec/tool-bar-spec.coffee
@@ -58,6 +58,17 @@
         button.setEnabled(false)
         expect(toolBar.children.length).toBe(1)
         expect(toolBar.firstChild.classList.contains('disabled')).toBe(true)
+      it 'clicking button', ->
+        toolBarAPI.addButton
+          icon: 'octoface'
+          callback: 'editor:select-line'
+          tooltip: 'Select line'
+        jasmine.attachToDOM(toolBar)
+        previouslyFocusedElement = document.activeElement
+        toolBar.children[0].dispatchEvent(new Event('mouseover'))
+        toolBar.children[0].focus()
+        toolBar.children[0].click()
+        expect(document.activeElement).toBe(previouslyFocusedElement)

     describe 'which can add a spacer', ->
       [toolBar] = []


Add test for app event-broker mixin.

Add test for app event-broker mixin.

--- test/spec/application_spec.coffee
+++ test/spec/application_spec.coffee
@@ -5,7 +5,8 @@
   'chaplin/lib/router'
   'chaplin/dispatcher'
   'chaplin/views/layout'
-], (_, mediator, Application, Router, Dispatcher, Layout) ->
+  'chaplin/lib/event_broker'
+], (_, mediator, Application, Router, Dispatcher, Layout, EventBroker) ->
   'use strict'

   describe 'Application', ->
@@ -16,6 +17,10 @@
     it 'should be a simple object', ->
       expect(app).to.be.an 'object'
       expect(app).to.be.a Application
+
+    it 'should mixin a EventBroker', ->
+      for own name, value of EventBroker
+        expect(app[name]).to.be EventBroker[name]

     it 'should initialize', ->
       expect(app.initialize).to.be.a 'function'


use keydown instead of triggering keydownEvent

--- spec/vim-mode-spec.coffee
+++ spec/vim-mode-spec.coffee
@@ -90,7 +90,7 @@
         keydown('d', element: editor[0])
         expect(vimState.opStack.length).toBe 1

-        editor.trigger keydownEvent('ctrl-c')
+        keydown('c', ctrl: true, element: editor[0])
         expect(vimState.opStack.length).toBe 0

     describe "the i keybinding", ->
@@ -864,7 +864,7 @@
     it "puts the editor into command mode when <ctrl-c> is pressed", ->
       expect(editor).not.toHaveClass 'command-mode'

-      editor.trigger keydownEvent('ctrl-c')
+      keydown('c', ctrl: true, element: editor[0])

       expect(editor).toHaveClass 'command-mode'
       expect(editor).not.toHaveClass 'insert-mode'


Use Markdown in onSave setting description

Dependent on atom/settings-view#618 being fixed

--- lib/index.coffee
+++ lib/index.coffee
@@ -7,10 +7,7 @@
       type: 'string'
       default: 'none'
       enum: ['none', 'tabify', 'untabify']
-      description: '''
-        Setting this to anything other than "none" can significantly impact the time it takes to
-        save large files.
-        '''
+      description: 'Setting this to anything other than `none` can **significantly** impact the time it takes to save large files.'

   # Public: Activates the package.
   activate: ->


Remove invalid argument

Remove invalid argument

--- lib/karen.coffee
+++ lib/karen.coffee
@@ -216,7 +216,7 @@
     if interval = @intervals[index - 1]
       interval.cleared = true

-  tick: (ms, callback, ehh) ->
+  tick: (ms, callback) ->
     ms = Math.floor(ms)

     nextToRun = =>


Refactor direct dependence on `latexmk` module

Extract the current direct usage of `latexmk` in latex::build(), behind a new
latex::getBuilder() method. This is the first step towards introducing new
LaTeX builders.

--- lib/latex.coffee
+++ lib/latex.coffee
@@ -21,9 +21,10 @@

     editor.save() if editor.isModified() # NOTE: Should this be configurable?

-    args = latexmk.constructArgs(file.path)
+    builder = @getBuilder()
+    args = builder.constructArgs(file.path)
     @showProgressIndicator()
-    proc = latexmk.run args, (statusCode) =>
+    proc = builder.run args, (statusCode) =>
       @destroyProgressIndicator()

       if statusCode == 0
@@ -32,6 +33,9 @@
         @showError("TeXification failed! Check the log file for more info...")

     return
+
+  getBuilder: ->
+    latexmk

   showResult: ->
     # TODO: Display a more visible success message.


HUGE UX IMPROVEMENT: disable scroll wheel zoom

--- client/src/views/map_view.coffee
+++ client/src/views/map_view.coffee
@@ -25,7 +25,8 @@

   renderMap: =>
     @map = L.map(
-      @$el.find(".map-visualisation")[0]
+      @$el.find(".map-visualisation")[0],
+      scrollWheelZoom: false
     )
     @map.fitBounds([
       [26.204734267107604, 57.44750976562499],


Delete login tokens after password change

--- app/storage.coffee
+++ app/storage.coffee
@@ -186,6 +186,11 @@
   # Change Password
   changePassword: (newPassword) =>
     deferred = Q.defer()
+    # Delete login tokens
+    redis.keys "token:#{@id}:*", (err, data) ->
+      for token in data
+        redis.del token
+    # Save password
     @_set("password", newPassword).then(deferred.resolve)
     deferred.promise


removing unused airRequest

removing unused airRequest

--- server/server.coffee
+++ server/server.coffee
@@ -9,7 +9,6 @@
 logger = require('./lib/logger')
 statsd = require('./lib/statsd')
 dataAdapter = require('./data_adapter')
-airRequest = require('./lib/airRequest')
 assetCompiler = require('./lib/assetCompiler')
 mw = require('./middleware')
 configureCallbacks = []


Add Sao Paulo config options

Add Sao Paulo config options

--- server_config.coffee
+++ server_config.coffee
@@ -3,6 +3,7 @@
 config.unittest = process.argv.indexOf('--unittest') > -1

 config.tokyo = process.env.TOKYO or false
+config.saoPaulo = process.env.SAOPAULO or false
 config.chinaDomain = "http://cn.codecombat.com"
 config.port = process.env.COCO_PORT or process.env.COCO_NODE_PORT or 3000
 config.ssl_port = process.env.COCO_SSL_PORT or process.env.COCO_SSL_NODE_PORT or 3443
@@ -22,8 +23,9 @@
   analytics_db: process.env.COCO_MONGO_ANALYTICS_DATABASE_NAME or 'analytics'
   mongoose_replica_string: process.env.COCO_MONGO_MONGOOSE_REPLICA_STRING or ''
   mongoose_tokyo_replica_string: process.env.COCO_MONGO_MONGOOSE_TOKYO_REPLICA_STRING or ''
+  mongoose_saoPaulo_replica_string : process.env.COCO_MONGO_MONGOOSE_SAOPAULO_REPLICA_STRING or ''

-if config.tokyo
+if config.tokyo or config.saoPaulo
   config.mongo.readpref = 'nearest'
 else
   config.mongo.readpref = 'primary'


flux: reactor checks for getterPath rather than constructor name

--- client/app/lib/flux/reactor.coffee
+++ client/app/lib/flux/reactor.coffee
@@ -31,7 +31,7 @@
 mapWithClassName = (classes) ->

   return classes.reduce (result, klass) ->
-    result[klass.name] = klass
+    result[klass.getterPath] = klass
     return result
   , {}


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_game_state.coffee
+++ src/engine_game_state.coffee
@@ -36,7 +36,7 @@
   # Just pass through to the current state
   add: (classNameOrEntityData, entityData) ->
     # Allow optional add "Class", data form
-    if classNameOrEntityData.isString?
+    if classNameOrEntityData.isString?()
       entityData.class = className
     else
       entityData = className


As hubot is frequently used in business settings, turn safe search to maximum (active) setting out of the box.

--- src/scripts/google-images.coffee
+++ src/scripts/google-images.coffee
@@ -29,7 +29,7 @@

 imageMe = (msg, query, cb) ->
   msg.http('http://ajax.googleapis.com/ajax/services/search/images')
-    .query(v: "1.0", rsz: '8', q: query)
+    .query(v: "1.0", rsz: '8', q: query, safe: 'active')
     .get() (err, res, body) ->
       images = JSON.parse(body)
       images = images.responseData.results


engine#add

engine#add

--- src/engine_game_state.coffee
+++ src/engine_game_state.coffee
@@ -37,7 +37,7 @@
     # Just pass through to the current state
     add: (className, entityData) ->
       if entityData?
-
+        entityData.class
       else

       self.trigger "beforeAdd", entityData


Link directly to the image URL when handling media

This is handy for those running textual with inline images turned on

--- src/scripts/tweet-content.coffee
+++ src/scripts/tweet-content.coffee
@@ -48,6 +48,6 @@
           tweet_text = tweet_text.replace(url.url, url.expanded_url)
       if tweet.entities.media?
         for media in tweet.entities.media
-          tweet_text = tweet_text.replace(media.url, media.expanded_url)
+          tweet_text = tweet_text.replace(media.url, media.media_url)

       msg.send "@#{tweet.user.screen_name}: #{tweet_text}"


Remove model builder visibility check

Remove model builder visibility check

--- src/ext/modules/proxy.coffee
+++ src/ext/modules/proxy.coffee
@@ -306,6 +306,7 @@
   requestExportModel = (key, path, overwrite, go) ->
     doGet "/99/Models.bin/#{encodeURIComponent key}?dir=#{encodeURIComponent path}&force=#{overwrite}", go

+  # TODO Obsolete
   requestModelBuildersVisibility = (go) ->
     doGet '/3/Configuration/ModelBuilders/visibility', unwrap go, (result) -> result.value

@@ -330,20 +331,21 @@
     if modelBuilders = getModelBuilders()
       go null, modelBuilders
     else
-      requestModelBuildersVisibility (error, visibility) ->
-        visibility = if error then 'Stable' else visibility
-        doGet "/3/ModelBuilders", unwrap go, (result) ->
-          builders = (builder for algo, builder of result.model_builders)
-          availableBuilders = switch visibility
-            when 'Stable'
-              for builder in builders when builder.visibility is visibility
-                builder
-            when 'Beta'
-              for builder in builders when builder.visibility is visibility or builder.visibility is 'Stable'
-                builder
-            else
-              builders
-          cacheModelBuilders availableBuilders
+      # requestModelBuildersVisibility (error, visibility) ->
+      #  visibility = if error then 'Stable' else visibility
+      visibility = 'Stable'
+      doGet "/3/ModelBuilders", unwrap go, (result) ->
+        builders = (builder for algo, builder of result.model_builders)
+        availableBuilders = switch visibility
+          when 'Stable'
+            for builder in builders when builder.visibility is visibility
+              builder
+          when 'Beta'
+            for builder in builders when builder.visibility is visibility or builder.visibility is 'Stable'
+              builder
+          else
+            builders
+        cacheModelBuilders availableBuilders

   requestModelBuilder = (algo, go) ->
     doGet getModelBuilderEndpoint(algo), go


some cleanup, renaming temporary variable from `_by` to `_step`


Fix compile method chain concat

Fix compile method chain concat

--- src/fs_utils/pipeline.coffee
+++ src/fs_utils/pipeline.coffee
@@ -61,7 +61,7 @@
         [compilerData, path, compilerCallback]
       compiler.compile.apply compiler, compilerArgs
   first = (next) -> next null, {source: initialData, path}
-  waterfall first.concat(chained), callback
+  waterfall [first].concat(chained), callback

 pipeline = (path, linters, compilers, callback) ->
   debug "Reading '#{path}'"


reload functionality moved to serverrun.coffee

--- bokehjs/src/coffee/server/serverutils.coffee
+++ bokehjs/src/coffee/server/serverutils.coffee
@@ -26,15 +26,6 @@
   exports.Promises = Promises

   utility =
-    reload : () ->
-      Config = require("common/base").Config
-      ping_url = "#{Config.prefix}/bokeh/ping"
-      $.get(ping_url)
-        .success(() ->
-          console.log('reloading')
-          window.location.reload())
-        .fail(_.delay((() -> utility.reload()), 1000))
-      return null
     load_one_object_chain : (docid, objid) ->
       if not exports.wswrapper?
         utility.make_websocket();
@@ -48,11 +39,6 @@
         load_models(all_models)
         apikey = data['apikey']
         submodels(exports.wswrapper, "bokehplot:#{docid}", apikey)
-        exports.wswrapper.subscribe("debug:debug", "")
-        exports.wswrapper.on('msg:debug:debug', (msg) ->
-          if msg == 'reload'
-            utility.reload()
-        )
       )
       return resp


Hide access tokens if user is not the project owner.

This prevents sneaky read-only users from sniffing out the read-write
link via the browser console.

--- services/web/app/coffee/Features/Editor/EditorHttpController.coffee
+++ services/web/app/coffee/Features/Editor/EditorHttpController.coffee
@@ -22,6 +22,9 @@
 		Metrics.inc "editor.join-project"
 		EditorHttpController._buildJoinProjectView req, project_id, user_id, (error, project, privilegeLevel) ->
 			return next(error) if error?
+			# Hide access tokens if this is not the project owner
+			if privilegeLevel != 'owner' && project.tokens?
+				project.tokens = {readOnly: '', readAndWrite: ''}
 			res.json {
 				project: project
 				privilegeLevel: privilegeLevel


try, catch the error

try, catch the error

--- client/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/app/Applications/Activity.kdapplication/AppController.coffee
@@ -181,8 +181,11 @@
         else
           new Date lastItemData.meta.createdAt

-        if isNan lastDate.getTime()
-          _rollbar.push msg:"bucket corrupted timestamp", item:lastItemData
+        try
+          if isNaN lastDate.getTime()
+            _rollbar.push msg:"bucket corrupted timestamp", item:lastItemData
+        catch error
+          console.log "bucket corrupted timestamp error"
       else
         lastDate = new Date


kick semaphone

kick semaphone

--- client/apps/edit/components/content/sections/image_set/index.coffee
+++ client/apps/edit/components/content/sections/image_set/index.coffee
@@ -1,7 +1,6 @@
 #
 # Image Set section that allows uploading a mix of images and artworks
 #
-
 _ = require 'underscore'
 gemup = require 'gemup'
 React = require 'react'


try to use travis

try to use travis


Send library name with graph and component

--- components/ConnectRuntime.coffee
+++ components/ConnectRuntime.coffee
@@ -39,11 +39,19 @@

   sendComponent: (runtime, component) ->
     return unless component.code
-    runtime.sendComponent 'source', component
+    runtime.sendComponent 'source',
+      name: component.name
+      language: component.language
+      library: component.project
+      code: component.code
+      tests: component.tests

   sendGraph: (runtime, graph) ->
     runtime.sendGraph 'clear',
       id: graph.id
+      name: graph.name
+      library: graph.project
+      main: (@project and graph.id is @project.main)
     for name, definition of graph.processes
       runtime.sendGraph 'addnode',
         id: name


Send event log and error as separate messages

--- components/RuntimeReducer.coffee
+++ components/RuntimeReducer.coffee
@@ -148,9 +148,11 @@
             runtimeEvents: events
       when 'runtime:error'
         events = addRuntimeEvent data.state, data.payload.runtime, 'error', data.payload
+        output.send
+          context:
+            runtimeEvents: events
         output.sendDone
           context:
-            runtimeEvents: events
             state: 'error'
             error: data.payload
         return


dont continue to process link for reading content link

--- tutor/src/components/book-content-mixin.cjsx
+++ tutor/src/components/book-content-mixin.cjsx
@@ -160,6 +160,8 @@
     @processLinks()

   componentWillUnmount: ->
+    @_linkContentIsMounted = false
+    @cleanUpLinks()
     @removeCanonicalLink()

   insertCanonicalLink: ->


lets be defensive about this delete

lets be defensive about this delete

--- collections/filter_artworks.coffee
+++ collections/filter_artworks.coffee
@@ -35,7 +35,7 @@
       aggregations[k] = @prepareAggregate v, k

     # remove this inferior for sale filter now, pls
-    delete aggregations['price_range']['*-*']
+    delete aggregations['price_range']?['*-*']

     aggregations


database: add periodic test query

database: add periodic test query

--- src/smc-hub/postgres-base.coffee
+++ src/smc-hub/postgres-base.coffee
@@ -88,6 +88,7 @@
         @_debug = opts.debug
         @_timeout_ms = opts.timeout_ms
         @_ensure_exists = opts.ensure_exists
+        @_init_test_query()
         dbg = @_dbg("constructor")  # must be after setting @_debug above
         dbg(opts)
         i = opts.host.indexOf(':')
@@ -115,6 +116,7 @@
     close: =>
         if @_state == 'closed'
             return  # nothing to do
+        @_close_test_query()
         @_state = 'closed'
         @emit('close')
         @removeAllListeners()
@@ -122,6 +124,29 @@
             @_client.removeAllListeners()
             @_client.end()
             delete @_client
+
+    ###
+    If @_timeout_ms is set, then we periodically do a simple test query,
+    to ensure that the database connection is working and responding to queries.
+    If the query below times out, then the connection will get recreated.
+    ###
+    _do_test_query: =>
+        dbg = @_dbg('test_query')
+        dbg('starting')
+        @_query
+            query : 'SELECT NOW()'
+            cb    : (err, result) =>
+                dbg("finished", err, result)
+
+    _init_test_query: =>
+        if not @_timeout_ms
+            return
+        @_test_query = setInterval(@_do_test_query, @_timeout_ms)
+
+    _close_test_query: =>
+        if @_test_query?
+            clearInterval(@_test_query)
+            delete @_test_query

     engine: -> 'postgresql'


slight style improvement regarding chat toggle...

--- src/smc-webapp/project_page.cjsx
+++ src/smc-webapp/project_page.cjsx
@@ -245,7 +245,6 @@
 CHAT_TOGGLE_STYLE =
     fontSize     : '14pt'
     position     : 'absolute'
-    top          : '1px'
     right        : '3px'
     zIndex       : 1000
     boxShadow    : '2px 2px 2px 2px #ccc'
@@ -294,7 +293,13 @@
         title    = <Icon name='comment'/><Space/> <Space/> {action} chat
         dir      = if @props.is_chat_open then 'down' else 'left'
         style    = misc.copy(CHAT_TOGGLE_STYLE)
-        style.right = if @props.fullscreen then '23px' else '3px'
+        if @props.fullscreen
+            style.top   = '1px'
+            style.right = '23px'
+        else
+            style.top   = '-30px'
+            style.right = '3px'
+

             <Tip
                 title     = {title}
@@ -511,43 +516,48 @@
             is_active    = {@props.active_project_tab == misc.path_to_tab(path)}
         />

+    render_file_tabs: (is_public) ->
+        shrink_fixed_tabs = $(window).width() < (376 + (@props.open_files_order.size + @props.num_ghost_file_tabs) * 250)
+
+

+
+                {[<FileTab
+                    name       = {k}
+                    label      = {v.label}
+                    icon       = {v.icon}
+                    tooltip    = {v.tooltip}
+                    project_id = {@props.project_id}
+                    is_active  = {@props.active_project_tab == k}
+                    shrink     = {shrink_fixed_tabs}
+                /> for k, v of fixed_project_pages when ((is_public and v.is_public) or (not is_public))]}
+
+            <SortableNav
+                className   = "smc-file-tabs-files-desktop"
+                helperClass = {'smc-file-tab-floating'}
+                onSortEnd   = {@on_sort_end}
+                axis        = {'x'}
+                lockAxis    = {'x'}
+                lockToContainerEdges={true}
+                distance    = {3 if not IS_MOBILE}
+                bsStyle     = "pills"
+                style       = {display:'flex', height:FILE_NAV_HEIGHT, overflowY:'hidden', paddingRight: '34px'}
+            >
+                {@file_tabs()}
+            </SortableNav>
+

+
     render : ->
         if not @props.open_files_order?
             return <Loading />

-        shrink_fixed_tabs = $(window).width() < 376 + (@props.open_files_order.size + @props.num_ghost_file_tabs) * 250
-
         group     = @props.get_my_group(@props.project_id)
-        is_public = (group == 'public')

             <FreeProjectWarning project_id={@props.project_id} name={name} />
-            {

-
-                    {[<FileTab
-                        name       = {k}
-                        label      = {v.label}
-                        icon       = {v.icon}
-                        tooltip    = {v.tooltip}
-                        project_id = {@props.project_id}
-                        is_active  = {@props.active_project_tab == k}
-                        shrink     = {shrink_fixed_tabs}
-                    /> for k, v of fixed_project_pages when ((is_public and v.is_public) or (not is_public))]}
-
-                <SortableNav
-                    className   = "smc-file-tabs-files-desktop"
-                    helperClass = {'smc-file-tab-floating'}
-                    onSortEnd   = {@on_sort_end}
-                    axis        = {'x'}
-                    lockAxis    = {'x'}
-                    lockToContainerEdges={true}
-                    distance    = {3 if not IS_MOBILE}
-                    bsStyle     = "pills"
-                    style       = {display:'flex', height:FILE_NAV_HEIGHT, overflowY:'hidden'}
-                >
-                    {@file_tabs()}
-                </SortableNav>
-
 if not @props.fullscreen}
+            {@render_file_tabs(group == 'public') if not @props.fullscreen}
             <ProjectMainContent
                 project_id      = {@props.project_id}
                 project_name    = {@props.name}


Fix padding mismatch on project dropdown

--- src/smc-webapp/projects_nav.cjsx
+++ src/smc-webapp/projects_nav.cjsx
@@ -289,7 +289,7 @@
         else
             title = "Open projects"

-        <Nav className='smc-dropdown-projects' style={display:'flex', margin:'0', flex:'1', fontSize:'25px', textAlign:'center', padding:'15px'}>
+        <Nav className='smc-dropdown-projects' style={display:'flex', margin:'0', flex:'1', fontSize:'25px', textAlign:'center', padding:'5px'}>
             <NavDropdown id="smc-top-project-nav-dropdown" title=title className="smc-projects-tabs" style={flex:'1'}>
                 {@project_menu_items()}
             </NavDropdown>


FIX: Correct replace to remove . from task ids

--- src/app/common/services/task-service.coffee
+++ src/app/common/services/task-service.coffee
@@ -474,7 +474,7 @@

   taskService.taskKeyToIdString = (task) ->
     key = task.taskKey()
-    "task-key-#{key.studentId}-#{key.taskDefAbbr}".replace(".", "")
+    "task-key-#{key.studentId}-#{key.taskDefAbbr}".replace(/[.#]/g, "-")

   taskService.taskKey = (task) ->
     {


FIX: Fix reference to taskService.gradeableStatuses

Wasn't being referenced at all by the updateTaskStatus function

--- src/app/common/services/task-service.coffee
+++ src/app/common/services/task-service.coffee
@@ -287,7 +287,7 @@
           alertService.add("danger", value.data.error, 6000)
           analyticsService.event 'Task Service', 'Failed to Update Task Status', status
     # Must provide grade if graded and in a final complete state
-    if task.definition.is_graded and status in ['complete', 'discuss', 'demonstrate']
+    if task.definition.is_graded and status in taskService.gradeableStatuses
       GradeTaskModal.show(task).result.then(
         # Grade was selected (modal closed with result)
         (selectedGrade) ->


Add error handling in mkdir_p

Add error handling in mkdir_p

--- services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee
+++ services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee
@@ -242,6 +242,7 @@
 			return folder.length != 0

 		ProjectGetter.getProjectWithOnlyFolders project_id, (err, project)=>
+			return callback(err) if err?
 			if path == '/'
 				logger.log project_id: project._id, "mkdir is only trying to make path of / so sending back root folder"
 				return callback(null, [], project.rootFolder[0])
@@ -255,9 +256,11 @@
 					parentFolder_id = parentFolder._id
 				builtUpPath = "#{builtUpPath}/#{folderName}"
 				projectLocator.findElementByPath project, builtUpPath, (err, foundFolder)=>
+					return callback(err) if err?
 					if !foundFolder?
 						logger.log path:path, project_id:project._id, folderName:folderName, "making folder from mkdirp"
 						@addFolder project_id, parentFolder_id, folderName, (err, newFolder, parentFolder_id)->
+							return callback(err) if err?
 							newFolder.parentFolder_id = parentFolder_id
 							previousFolders.push newFolder
 							callback null, previousFolders


removed console.log

removed console.log

--- services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee
+++ services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee
@@ -111,7 +111,6 @@
 		DocstoreManager.getDoc project_id, doc_id, options, callback

 	addDoc: (project_id, folder_id, docName, docLines, callback = (error, doc, folder_id) ->)=>
-		console.log project_id
 		ProjectGetter.getProjectWithOnlyFolders project_id, (err, project) ->
 			logger.log project: project._id, folder_id: folder_id, doc_name: docName, "adding doc"
 			return callback(err) if err?


Update projects

Update projects

--- atom/projects.cson
+++ atom/projects.cson
@@ -1,45 +1,28 @@
 [
   {
-    title: "dev"
+    title: "data"
     paths: [
-      "/Users/rogier/Dropbox/git/blijblijblij.github.io"
-      "/Users/rogier/Dropbox/git/blijblijblij.github.io-hugo"
-      "/Users/rogier/Dropbox/git/learning-ruby"
-      "/Users/rogier/Dropbox/git/meia-ontwerp-html"
-      "/Users/rogier/Dropbox/git/meia-ontwerp-rails"
-      "/Users/rogier/Dropbox/git/playground"
-      "/Users/rogier/git/dotfiles"
-      "/Users/rogier/Dropbox/git/blijblijblij.com"
-      "/Users/rogier/Dropbox/git/blijblijblij.com-rails"
+      "/home/rogier/git/rm-etl"
+      "/home/rogier/git/rm-pentaho"
     ]
   }
   {
-    title: "rm data"
+    title: "ror"
     paths: [
-      "/Users/rogier/git/rm-etl"
-      "/Users/rogier/git/rm-pentaho"
+      "/home/rogier/git/rm-data-admin"
+      "/home/rogier/git/rm-masterdata-engine"
+      "/home/rogier/git/rm-rails"
+      "/home/rogier/git/rm-oidc-engine"
     ]
   }
   {
-    title: "rm rails"
+    title: "var"
     paths: [
-      "/Users/rogier/git/rm-content-admin"
-      "/Users/rogier/git/rm-data-admin"
-      "/Users/rogier/git/rm-masterdata-engine"
-      "/Users/rogier/git/rm-rails"
-      "/Users/rogier/git/rm-oidc-engine"
-      "/Users/rogier/git/rm-dashboard-assets"
-    ]
-  }
-  {
-    title: "rm var"
-    paths: [
-      "/Users/rogier/git/rm-data-dev"
-      "/Users/rogier/git/rm-docker"
-      "/Users/rogier/git/rm-mesos"
-      "/Users/rogier/git/rm-scripts"
-      "/Users/rogier/git/rm-secrets"
-      "/Users/rogier/git/rm-system"
+      "/home/rogier/git/rm-docker"
+      "/home/rogier/git/rm-mesos"
+      "/home/rogier/git/rm-scripts"
+      "/home/rogier/git/rm-secrets"
+      "/home/rogier/git/rm-system"
     ]
   }
 ]


Update snippets.json

Update snippets.json

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -1,91 +1,20 @@
-# Your snippets
-#
-# Atom snippets allow you to enter a simple prefix in the editor and hit tab to
-# expand the prefix into a larger code block with templated values.
-#
-# You can create a new snippet in this file by typing "snip" and then hitting
-# tab.
-#
 '.text.html.basic':
   'a link':
     'prefix': 'alink'
     'body': '$2'
-  'register link':
-    'prefix': 'registerLink'
-    'body': ' $2'
-  'talk left':
-    'prefix': 'tleft'
-    'body': '

${1:メッセージ}

'
-  'talk right':
-    'prefix': 'tright'
-    'body': '

${1:メッセージ}

'
-  'talk end':
-    'prefix': 'tend'
-    'body': '

'
-  'center':
-    'prefix': 'center'
-    'body': '
\n  $1\n
'
-  'ul rich':
-    'prefix': 'ulrich'
-    'body': '
\n
\n
$1
\n
\n
'
-  'ol rich':
-    'prefix': 'olrich'
-    'body': '
\n
\n
$1
\n
\n
'
-  'reference':
-    'prefix': 'reference'
-    'body': '参考'
-  'huto':
-    'prefix': 'huto'
-    'body': '$1'
-  'hutoaka':
-    'prefix': 'hutoaka'
-    'body': ''
-  'ymarker':
-    'prefix': 'ymarker'
-    'body': ''
-  'rmarker':
-    'prefix': 'rmarker'
-    'body': ''
-  'oomozi':
-    'prefix': 'oomozi'
-    'body': '$1'
   'zaif':
     'prefix': 'zaif'
     'body': 'Zaif(ザイフ)'
   'coincheck':
     'prefix': 'coincheck'
     'body': 'Coincheck(コインチェック)'
-  'bitbank':
-    'prefix': 'bitbank'
-    'body': 'bitbank(ビットバンク)'
   'bitflyer':
     'prefix': 'bitflyer'
     'body': 'bitFlyer(ビットフライヤー)'
-  'binance':
-    'prefix': 'binance'
-    'body': 'Binance(バイナンス)'
-  'cryptopia':
-    'prefix': 'cryptopia'
-    'body': 'Cryptopia(クリプトピア)'
-  'kucoin':
-    'prefix': 'kucoin'
-    'body': 'KuCoin(クーコイン)'
-  'mercatox':
-    'prefix': 'mercatox'
-    'body': 'MERCATOX(メルカトックス)'
-  'exx.com':
-    'prefix': 'exxcom'
-    'body': 'EXX.com'
-  'reddit':
-    'prefix': 'reddit'
-    'body': 'Reddit(レディット)'
-  'quoinex':
-    'prefix': 'quoinex'
-    'body': 'QUOINEX'
-  'tablefull':
-    'prefix': 'tablefull'
-    'body': '
\n
\n
\n順位	\n
\n
\n
\n
\n1	\n
\n
\n
'
-#
-# Atom Flight Manual:
-# http://flight-manual.atom.io/using-atom/sections/basic-customization/#_cson
+  'sobig':
+    'prefix': 'sobig'
+    'body': '$1'
+  'big':
+    'prefix': 'big'
+    'body': '$1'


Add Mocha assertion snippets

Add Mocha assertion snippets

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -230,7 +230,7 @@
     'prefix': 'qsa'
     'body': 'querySelectorAll($1)$2'

-  # Jasmine
+  # Mocha

   'it':
     'prefix': 'it'
@@ -242,7 +242,23 @@

   'expect':
     'prefix': 'expect'
-    'body': 'expect($1).${2:toBe}($3);'
+    'body': 'expect($1).$2'
+
+  'to be':
+    'prefix': 'tobe'
+    'body': 'to.be.'
+
+  'not to be':
+    'prefix': 'nottobe'
+    'body': 'not.to.be.'
+
+  'to equal':
+    'prefix': 'toequal'
+    'body': 'to.equal($1)$2'
+
+  'not to equal':
+    'prefix': 'nottoequal'
+    'body': 'not.to.equal($1);$2'

 # Awk


That should fail

That should fail

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -246,8 +246,7 @@

   # Sometimes you just don't want any of them hanging around.
   trimBlankLines: (string) ->
-    if typeof string == 'string'
-      string.replace(/^[\r\n]+/, '').replace(/[\r\n]+$/, '')
+    string.replace(/^[\r\n]+/, '').replace(/[\r\n]+$/, '')

   # Given a title, convert it into a URL-friendly slug.
   slugifyTitle: (string) ->


:bug: Point is not imported (rarely evaluated code path, so harmless than it seem to)

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -1,4 +1,4 @@
-{Disposable} = require 'atom'
+{Disposable, Point} = require 'atom'
 _ = require 'underscore-plus'

 getAdjacentPaneForPane = (pane) ->


DevTools: Support group routes

DevTools: Support group routes

--- client/DevTools/AppController.coffee
+++ client/DevTools/AppController.coffee
@@ -2,7 +2,7 @@

   name    = "DevTools"
   version = "0.1"
-  route   = "/#{name}"
+  route   = "/:name?/#{name}"

   KD.registerAppClass this, {name, version, behavior: "application", route}


Prevent flash when reading message

Prevent flash when reading message

--- client/app/components/message.coffee
+++ client/app/components/message.coffee
@@ -197,7 +197,7 @@
                     @renderCompose()
                     MessageContent
                         ref: 'messageContent'
-                        message: message
+                        messageID: message.get 'id'
                         messageDisplayHTML: messageDisplayHTML
                         html: @_htmlContent
                         text: prepared.text
@@ -626,8 +626,8 @@
                                 onClick: @props.displayImages,
                                 t 'message images display'
                 iframe
-                    'data-message-id': @props.message.get 'id'
-                    name: "frame-#{@props.message.get 'id'}"
+                    'data-message-id': @props.messageID
+                    name: "frame-#{@props.messageID}"
                     className: 'content',
                     ref: 'content',
                     allowTransparency: true,
@@ -661,9 +661,9 @@
                 doc = frame.contentDocument or frame.contentWindow?.document
                 if doc?
                     doc.documentElement.innerHTML = @props.html
-                    window.cozyMails.customEvent "MESSAGE_LOADED", @props.message.toJS()
+                    window.cozyMails.customEvent "MESSAGE_LOADED", @props.messageID
                     updateHeight = (e) ->
-                        height = doc.documentElement.getBoundingClientRect().height
+                        height = doc.documentElement.scrollHeight
                         if height < 60
                             frame.style.height = "60px"
                         else
@@ -675,7 +675,6 @@
                             doc.body.removeEventListener 'load', loadContent
                             frame.contentWindow?.removeEventListener 'resize'

-                    frame.style.height = "32px"
                     updateHeight()
                     setTimeout updateHeight, 1000
                     doc.body.onload = updateHeight
@@ -693,7 +692,7 @@
             else
                 loadContent()
         else
-            window.cozyMails.customEvent "MESSAGE_LOADED", @props.message.toJS()
+            window.cozyMails.customEvent "MESSAGE_LOADED", @props.messageID

     componentDidMount: ->


increased scale of force due to gravity for faster flow speeds

--- test/rainflow/src/rainflow.coffee
+++ test/rainflow/src/rainflow.coffee
@@ -49,7 +49,7 @@
     V = (r) => # energy evaluation function
       # bilinearly interpolated energy, see http://en.wikipedia.org/wiki/Bilinear_interpolation#Algorithm
       # first wrap the input coordinates to an interior point, enforcing periodic boundary conditions
-      scale = 1e-4 # units
+      scale = 3e-4 # units
       x   = r.x
       y   = r.y
       x   = @elevation[0].length - 1 + x % (@elevation[0].length - 1) if x < 0


Help text updates from Trish Closes #19

Help text updates from Trish
Closes #19

--- examples.coffee
+++ examples.coffee
@@ -24,15 +24,16 @@
   },{
     label: "Keywords"
     content: '''
-
Add general keywords or phrases about the primary species or subjects depicted in the  illustration Separate terms by semi-colons e.g. bird; landscape; crab; forest; man; woman; apple; pottery; cemetery; skull; fossil; microscopic view; meteor, meteorological observations.

-
If it is a chart or table, indicate the topic that the data represents e.g. Montana Wheat; Trout length; Livestock inventory; mirror

+
Add general keywords or phrases about the primary species or subjects depicted in the illustration. Separate terms by semi-colons. Keywords can be based on information from a caption, when available, or simply your observation of objects in the image. You do not need to repeat information from the species, inscription and contributor task. Add keywords in your native language.

+
e.g. bird; landscape; crab; forest; man; woman; apple; pottery; cemetery; skull; fossil; microscopic view; meteor, meteorological observations.

+
If it is a chart or table, indicate the topic that the data represents e.g. Montana Wheat; Trout length; Livestock inventory; mirror. The topic may be indicated in a caption or in the surrounding text of the page. You do not need to transcribe all the data in the table.

If is it a map, add any current or historic geographic locations indicated by the map or caption e.g. Three Lakes; Southern China; Canada; Uriconium; Yorkshire

                  '''
     figures: []
   },{
     label: "Tagging"
     content: '''
-
Click on species, inscriptions and contributors in the illustration to record each one.

+
Click on species, inscriptions and contributors in the illustration to record each one. These will often be blank for Charts/Tables and Maps i.e. you do not need to transcribe all the data in a table. If a species is not indicated in a caption you may find the species name in the text surrounding the illustration or on the pages before and after the page you are describing. To see the full book or journal click on the page () link.

                  '''
     figures: [{
       image: "examples/species.jpg"


Made defs available to postrenders' scope.

--- lib/zappa.coffee
+++ lib/zappa.coffee
@@ -219,7 +219,7 @@
     @locals.redirect = @redirect
     @locals.send = @send
     @locals.puts = puts
-
+
     for k, v of @defs
       @locals[k] = v

@@ -227,6 +227,7 @@
       @locals[k] = ->
         v(@context, @, arguments)

+    @locals.defs = @defs
     @locals.postrenders = @postrenders
     @locals.views = @views
     @locals.layouts = @layouts
@@ -280,7 +281,7 @@
       postrender = @postrenders[options.apply]
       body = jquery('body')
       body.empty().html(result)
-      postrender opts.context, $: jquery
+      postrender opts.context, jquery.extend(@defs, {$: jquery})
       result = body.html()

     if options.layout
@@ -315,6 +316,7 @@
       @locals[k] = ->
         v(@context, @, arguments)

+    @locals.defs = @app.defs
     @locals.postrenders = @app.postrenders
     @locals.views = @app.views
     @locals.layouts = @app.layouts
@@ -358,7 +360,7 @@
       postrender = @postrenders[options.apply]
       body = jquery('body')
       body.empty().html(result)
-      postrender opts.context, $: jquery
+      postrender opts.context, jquery.extend(@defs, {$: jquery})
       result = body.html()

     if options.layout


Updated tests to new syntax

Updated tests to new syntax

--- test/tests/LayerStatesTest.coffee
+++ test/tests/LayerStatesTest.coffee
@@ -6,30 +6,32 @@

 		beforeEach ->
 			@layer = new Layer()
-			@layer.states.add("a", {x:100, y:100})
-			@layer.states.add("b", {x:200, y:200})
+			@layer.states.a = {x:100, y:100}
+			@layer.states.b = {x:200, y:200}

 		it "should emit willSwitch when switching", (done) ->

 			test = (previous, current, states) =>
-				previous.should.equal 'default'
+				previous.should.equal 'initial'
 				current.should.equal 'a'
-				@layer.states.state.should.equal 'default'
+				@layer.states.currentName.should.equal 'initial'
+				@layer.states.current.should.equal @layer.states.initial
 				done()

 			@layer.states.on 'willSwitch', test
-			@layer.states.switchInstant 'a'
+			@layer.animateTo 'a', instant: true

 		it "should emit didSwitch when switching", (done) ->

 			test = (previous, current, states) =>
-				previous.should.equal 'default'
+				previous.should.equal 'initial'
 				current.should.equal 'a'
-				@layer.states.state.should.equal 'a'
+				@layer.states.currentName.should.equal 'a'
+				@layer.states.current.should.equal @layer.states.a
 				done()

 			@layer.states.on 'didSwitch', test
-			@layer.states.switchInstant 'a'
+			@layer.animateTo 'a', instant: true

 	describe "Defaults", ->
@@ -37,30 +39,29 @@
 		it "should set defaults", ->

 			layer = new Layer
-			layer.states.add "test", {x:123}
-			layer.states.switch "test"
-
-			layer.states._animation.options.curve.should.equal Framer.Defaults.Animation.curve
+			layer.states.test = {x:123}
+			animation = layer.animateTo "test"
+
+			animation.options.curve.should.equal Framer.Defaults.Animation.curve

 			Framer.Defaults.Animation =
 				curve: "spring(1, 2, 3)"

 			layer = new Layer
-			layer.states.add "test", {x:456}
-			layer.states.switch "test"
-
-			layer.states._animation.options.curve.should.equal "spring(1, 2, 3)"
+			layer.states.test = {x:456}
+			animation = layer.animateTo "test"
+
+			animation.options.curve.should.equal "spring(1, 2, 3)"

 			Framer.resetDefaults()

-		it "should convert string to colors in states", ->
-			layer = new Layer
-			layer.states.add
-				test:
-					backgroundColor:"fff"
-					color: "000"
-			layer.states._states.test.backgroundColor.isEqual(new Color("fff")).should.be.true
-			layer.states._states.test.color.isEqual(new Color("000")).should.be.true
+		# it "should convert string to colors in states", ->
+		# 	layer = new Layer
+		# 	layer.states.test =
+		# 			backgroundColor:"fff"
+		# 			color: "000"
+		# 	layer.states.test.backgroundColor.isEqual(new Color("fff")).should.be.true
+		# 	layer.states.test.color.isEqual(new Color("000")).should.be.true

@@ -69,44 +70,56 @@
 		it "should switch instant", ->

 			layer = new Layer
-			layer.states.add
-				stateA: {x:123}
-				stateB: {y:123}
-
-			layer.states.switchInstant "stateA"
-			layer.states.current.should.equal "stateA"
+			layer.states =
+				stateA:
+					x:123
+				stateB:
+					y:123
+					options:
+						instant: true
+
+			layer.animateTo "stateA",
+				instant: true
+			layer.states.currentName.should.equal "stateA"
 			layer.x.should.equal 123

-			layer.states.switchInstant "stateB"
-			layer.states.current.should.equal "stateB"
+			layer.animateTo "stateB"
+			layer.states.currentName.should.equal "stateB"
 			layer.y.should.equal 123

 		it "should not change html when using switch instant", ->
 			layer = new Layer
 				html: "fff"
-			layer.states.switchInstant('default')
+			layer.states.stateA = {x: 100}
+			layer.animateTo 'stateA', instant: true
 			layer.html.should.equal "fff"
+
+		it "should not convert html to a color value if used in a state", ->
+			layer = new Layer
+			layer.states.stateA = {x: 100, html: 'aaa'}
+			layer.animateTo 'stateA', instant: true
+			layer.html.should.equal "aaa"

 		it "should not change style when going back to default", ->
 			layer = new Layer
 			layer.style.fontFamily = "Arial"
 			layer.style.fontFamily.should.equal "Arial"

-			layer.states.add
+			layer.states =
 				test: {x: 500}

-			layer.states.switchInstant("test")
+			layer.animateTo "test", instant: true
 			layer.x.should.equal 500
 			layer.style.fontFamily = "Helvetica"
 			layer.style.fontFamily.should.equal "Helvetica"

-			layer.states.switchInstant("default")
+			layer.animateTo "initial", instant: true
 			layer.x.should.equal 0
 			layer.style.fontFamily.should.equal "Helvetica"

 	describe "Properties", ->

-		it "should bring back the 'default' state values when using 'next'", (done) ->
+		it.skip "should bring back the 'default' state values when using 'next'", (done) ->

 			layer = new Layer
 			layer.states.add
@@ -139,23 +152,23 @@
 		it "should set scroll property", ->

 			layer = new Layer
-			layer.states.add
+			layer.states =
 				stateA: {scroll:true}
 				stateB: {scroll:false}

-			layer.states.switchInstant "stateA"
+			layer.animateTo "stateA", instant: true
 			layer.scroll.should.equal true

-			layer.states.switchInstant "stateB"
+			layer.animateTo "stateB", instant: true
 			layer.scroll.should.equal false

-			layer.states.switchInstant "stateA"
+			layer.animateTo "stateA", instant: true
 			layer.scroll.should.equal true

 		it "should set non numeric properties with animation", (done) ->

 			layer = new Layer
-			layer.states.add
+			layer.states =
 				stateA: {scroll:true, backgroundColor:"red"}

 			layer.scroll.should.equal false
@@ -165,12 +178,12 @@
 				layer.style.backgroundColor.should.equal new Color("red").toString()
 				done()

-			layer.states.switch "stateA"
+			layer.animateTo "stateA"

 		it "should set non and numeric properties with animation", (done) ->

 			layer = new Layer
-			layer.states.add
+			layer.states =
 				stateA: {x:200, backgroundColor:"red"}

 			# layer.scroll.should.equal false
@@ -182,20 +195,20 @@
 				layer.style.backgroundColor.should.equal new Color("red").toString()
 				done()

-			layer.states.switch "stateA", {curve:"linear", time:0.1}
+			layer.animateTo "stateA", {curve:"linear", time:0.1}

 		it "should restore the default state when using non exportable properties", ->

 			layer = new Layer
-			layer.states.add
+			layer.states =
 				stateA: {midX:200}

 			layer.x.should.equal 0

-			layer.states.switchInstant "stateA"
+			layer.animateTo "stateA", instant: true
 			layer.x.should.equal 200 - (layer.width // 2)

-			layer.states.switchInstant "default"
+			layer.animateTo "initial", instant: true
 			layer.x.should.equal 0

 		it "should set the parent", ->
@@ -203,15 +216,19 @@
 			layerA = new Layer
 			layerB = new Layer
 				parent: layerA
-
-			layerB.states.add
-				stateA:
+			layerC = new Layer
+
+			layerB.states =
+				noParent:
 					parent: null
+				parentC:
+					parent: layerC

 			assert.equal(layerB.parent, layerA)
-
-			layerB.states.switchInstant "stateA"
+			layerB.animateTo "parentC", instant: true
+			assert.equal(layerB.parent, layerC)
+			layerB.animateTo "noParent", instant: true
 			assert.equal(layerB.parent, null)

-			layerB.states.switchInstant "default"
+			layerB.animateTo "initial", instant: true
 			# assert.equal(layerB.parent, layerA)


client: stackeditor: wizard moved to modal initial.

--- client/stack-editor/lib/index.coffee
+++ client/stack-editor/lib/index.coffee
@@ -31,7 +31,9 @@

   openStackWizard: ->

-    scrollView = new kd.ScrollView
+    modal = new kd.ModalView
+      width : 1000
+
     view = new OnboardingView

     view.on 'StackOnboardingCompleted', (result) =>
@@ -43,14 +45,14 @@
       if result?.selectedProvider
         overrides = _.assign overrides, { selectedProvider: result.selectedProvider }

-      EnvironmentFlux.actions.createStackTemplateWithDefaults()
-        .then ({ stackTemplate }) ->
-          kd.singletons.router.handleRoute "/Stack-Editor/#{stackTemplate._id}"
+      modal.destroy()

-    scrollView.addSubView view
+    modal.addSubView view

-    @mainView.destroySubViews()
-    @mainView.addSubView scrollView
+    EnvironmentFlux.actions.createStackTemplateWithDefaults()
+      .then ({ stackTemplate }) ->
+        kd.singletons.router.handleRoute "/Stack-Editor/#{stackTemplate._id}"
+

   createView: (stackTemplate) ->


Make linter happier

Make linter happier

--- src/RemoteSubGraph.coffee
+++ src/RemoteSubGraph.coffee
@@ -54,7 +54,7 @@
           @graph = graph
           @graphName = graph.name or graph.properties.id
           @runtime.setMain graph
-          connection.sendGraph graph, @runtime, =>
+          connection.sendGraph graph, @runtime, ->
             return
           , true

@@ -66,10 +66,9 @@
       else if msg.command == 'packet'
         @onPacketReceived msg.payload

-    @runtime.on 'connected', () =>
-      #
-    @runtime.on 'error', () =>
-      console.log 'error'
+    @runtime.on 'connected', ->
+    @runtime.on 'error', (err) ->
+      console.log 'error', err

     # Attempt to connect
     @runtime.connect()


added one-time color manipulation for use in debugging

--- evolution.coffee
+++ evolution.coffee
@@ -830,9 +830,9 @@
   stationary: -> !( @velocity[0] || @velocity[1] )
   move: ->
   ctx: -> @context ?= @universe.ctx
-  draw: -> @drawBody()
+  draw: (color=@bodyColor) -> @drawBody(color)
   outline: (color) -> @outlined = color
-  drawBody: ->
+  drawBody: (color=@bodyColor) ->
     if @outlined
       @drawCircle(
         @x
@@ -845,7 +845,7 @@
       @x
       @y
       @radius
-      @bodyColor || 'black'
+      color || 'black'
     )
   drag: ->
   drawCircle: ( x, y, radius, color ) -> @drawArc x, y, radius, color, 0, TAU
@@ -957,8 +957,8 @@
     genes[k] = v for k, v of base
     genes[k] = v for k, v of ext
     genes
-  draw: ->
-    super()
+  draw: (color=@bodyColor) ->
+    super(color)
     @drawHunger()
   drawHunger: -> # show emptiness of belly
     h = @health()
@@ -1206,8 +1206,8 @@
     x = 2 + @radius + @earSize
     y = @eyeSize / 2  + @radius
     [ width, height, x, y ]
-  draw: ->
-    super()
+  draw: (color=@bodyColor) ->
+    super(color)
     @drawHead()
     @tailSize ?= @calcTailSize()
     @drawTail()


Fixed clearDelay().

Fixed clearDelay().

--- app/assets/javascripts/app/controllers/_application_controller.js.coffee
+++ app/assets/javascripts/app/controllers/_application_controller.js.coffee
@@ -182,22 +182,22 @@
     # check global var
     if !@_delayID
       @_delayID = {}
-
     clearTimeout( @_delayID[delay_id] ) if @_delayID[delay_id]

   delay: (callback, timeout, delay_id) =>

-    # check global var
-    if !@_delayID
-      @_delayID = {}
-
     # clear auto save
-    @clearDelay( @_delayID[delay_id] )
+    @clearDelay( delay_id )

     # request new data
     call = =>
       callback()
     if delay_id
+
+      # check global var
+      if !@_delayID
+        @_delayID = {}
+
       @_delayID[delay_id] = setTimeout( call, timeout )
     else
       setTimeout( call, timeout )


[improvement] Sorted tickets unable to be estimated down with estimated tickets in Estimate Effort (5m)

--- app/assets/javascripts/houston-scheduler/views/_edit_tickets_view.coffee
+++ app/assets/javascripts/houston-scheduler/views/_edit_tickets_view.coffee
@@ -37,6 +37,7 @@
     @tickets
       .sortBy (ticket)=>
         (+ticket.get('estimatedEffort') > 0) * 99999999 + # put tickets _with_ estimates at the bottom,
+        (+!!ticket.get('unableToSetEstimatedEffort') > 0) * 99999999 +
         (+ticket.get('sequence') <= 0) * 9999999 +        # then tickets with no priority,
          +ticket.get('sequence')                          # finally sort by priority
       .each (ticket)=>


[fix] Prohibited underscores in tags (4m)

--- app/assets/javascripts/houston/feedback/helpers/jquery_extensions.coffee
+++ app/assets/javascripts/houston/feedback/helpers/jquery_extensions.coffee
@@ -30,7 +30,7 @@
     tags = _.reduce text.split(/[,;]/), (tags, tag) ->

       # Normalize tags
-      tag = tag.trim().toLowerCase().replace(/^#/, '').replace(/[^\w\/\?]+/g, '-')
+      tag = tag.trim().toLowerCase().replace(/^#/, '').replace(/[^a-z0-9\/\?]+/g, '-')

       # Convert "feature/subfeature" to ["feature", "feature-subfeature"]
       tags.concat _.reduce tag.split(/\//), (tags, tag)->


#830 Implement create cell button behavior

--- app/assets/javascripts/oxalis/view/action-bar/volume_actions_view.coffee
+++ app/assets/javascripts/oxalis/view/action-bar/volume_actions_view.coffee
@@ -30,7 +30,7 @@

   createCell : ->

-    throw new Error("TODO. Not implemented yet.")
+    @model.volumeTracing.createCell()

   changeMode : (evt) ->


projection validation bugfix

projection validation bugfix

--- fetching.coffee
+++ fetching.coffee
@@ -161,7 +161,7 @@
             if sKey isnt '_id' and sKey[0] isnt '$'
                 ok = false
                 for fKey, fValue of inclusionSet
-                    if fKey.split('.')[0] is sKey
+                    if fKey.split('.')[0] is sKey.split('.')[0]
                         ok = true
                         break
                 if not ok
@@ -174,7 +174,7 @@
             if sKey isnt '_id' and sKey[0] isnt '$'
                 ok = false
                 for fKey, fValue of inclusionSet
-                    if fKey.split('.')[0] is sKey
+                    if fKey.split('.')[0] is sKey.split('.')[0]
                         ok = true
                         break
                 if not ok
@@ -328,7 +328,6 @@
                 # has an attached entry with that _id and no other parts of the selector
                 # rule out the one possible match.
                 options = @_qsToMongoOptions(qs)
-                console.log 'options', options
                 options.transform = false
                 options.reactive = false
                 doc = modelClass.findOne(qs.selector, options)
@@ -360,7 +359,7 @@
             false

         ret = helper()
-        console.debug 'isQueryReady', ret, qsString
+        # console.debug 'isQueryReady', ret, qsString
         ret


Fixed hi scores not displaying correctly

--- web/scripts/gameSelection.coffee
+++ web/scripts/gameSelection.coffee
@@ -55,7 +55,7 @@
 		else
 			$(tmp1).text("Status:  Incomplete")

-		$(tmp2).text("Hi-Score:  #{if player? player.hiscore else 0}")
+		$(tmp2).text("Hi-Score: #{if player? then player.hiscore else 0}")

 		if @dis
 			$(tmp3).text("Stars: #{player?.stars}")


dammit

dammit

--- shared/code/scraperwiki-2.coffee
+++ shared/code/scraperwiki-2.coffee
@@ -183,8 +183,8 @@
       apikey: settings.source.apikey
       cmd: cmd
     complete: ->
-      scraperwiki.exec.pending -= 1
-      if scraperwiki.exec.pending is 0
+      scraperwiki.tool.exec.pending -= 1
+      if scraperwiki.tool.exec.pending is 0
         $(document).trigger('execsComplete')
   if success?
     options.success = success


Refactor tip appearance methods with promises.

Also factor out some accessor logic. Other minor refactoring included.

Also remove the 'safeToggle' option, since it really isn't one.

--- src/js/jquery.hlf.tip.coffee
+++ src/js/jquery.hlf.tip.coffee
@@ -79,8 +79,6 @@
       #   direction. Note that the direction data structure must be an array of
       #   `components`, and conventionally with top/bottom first.
       defaultDirection: ['bottom', 'right']
-      # - `safeToggle` prevents orphan tips, since timers are sometimes unreliable.
-      safeToggle: on
       # - `tipTemplate` should return interpolated html when given the
       #   additional container class list. Its context is the plugin instance.
       tipTemplate: (containerClass) ->
@@ -229,11 +227,29 @@
         _.include(@defaultDirection, directionComponent)
       )

-    # 𝒇 `_setState` is a simple setter that returns false if state doesn't change.
-    _setState: (state) ->
+    # 𝒇 `_setCurrentTrigger` is a simple setter that updates a trigger and makes
+    # it current, but only if it isn't already.
+    _setCurrentTrigger: ($trigger) ->
+      @_triggerChanged = not $trigger.is @_$currentTrigger
+      return no unless @_triggerChanged
+      @_inflateByTrigger $trigger
+      @_$currentTrigger = $trigger
+
+
+    # 𝒇 `_setState` is a simple setter that returns false if state doesn't
+    # change. It also handles minor tasks when switching to a new state.
+    _setState: (state, data...) ->
       return no if state is @_state
+      #- previous = @_state
       @_state = state
       @debugLog @_state
+      switch state
+        when 'asleep' then @afterHide.apply @, data
+        when 'awake' then @afterShow.apply @, data
+        when 'sleeping' then clearTimeout @_wakeCountdown
+        when 'waking'
+          clearTimeout @_sleepCountdown
+          @_triggerChanged = yes

     # 𝒇 `_setTip` aliases the conventional `$el` property to `$tip` for clarity.
     _setTip: ($tip) => @$tip = @$el = $tip
@@ -287,70 +303,75 @@

     # ### Appearance

-    # 𝒇 `wakeByTrigger` is the main toggler and a `_state` updater. It takes an
-    # `onWake` callback, which is usually to update position. The toggling and
-    # main changes only happen if the delay is passed. It will return a bool for
-    # success.
-    wakeByTrigger: ($trigger, event, onWake) ->
-      # - Store current trigger info.
-      triggerChanged = not $trigger.is @_$currentTrigger
-      if triggerChanged
-        @_inflateByTrigger $trigger
-        @_$currentTrigger = $trigger
+    # 𝒇 `wakeByTrigger` is the main toggler and a `_state` updater. The toggling and
+    # main changes only happen if the delay is passed. It will return a promise that
+    # fails if waking gets skipped; it also may become done synchronously.
+    wakeByTrigger: ($trigger, event) ->
+      deferred = $.Deferred()
+      promise = deferred.promise()
+      @_setCurrentTrigger $trigger
+
       # - Go directly to the position updating if no toggling is needed.
+      updateBeforeWake = =>
+        @_positionToTrigger $trigger, event
+        @onShow event # Hook in custom logic.
       if @_state is 'awake'
-        @_positionToTrigger $trigger, event
-        @onShow triggerChanged, event
-        if onWake? then onWake()
         @debugLog 'quick update'
-        return yes
-      if event? then @debugLog event.type
+        updateBeforeWake()
+        deferred.resolve()
+        return promise
+
+      @debugLog event.type if event? # Log the event if we made it this far.
       # - Don't toggle if awake or waking, or if event isn't `truemouseenter`.
-      return no if @_state in ['awake', 'waking']
-      # - Get delay and initial duration.
-      delay = @animations.show.delay
-      duration = @animations.show.duration
+      if @_state in ['awake', 'waking']
+        deferred.reject()
+        return promise
+
       # - Our `wake` subroutine runs the timed-out logic, which includes the fade
-      #   transition. The latter is also affected by `safeToggle`. The `onShow`
-      #   and `afterShow` hook methods are also run.
-      wake = =>
-        @_positionToTrigger $trigger, event
-        @onShow triggerChanged, event
+      #   transition. Ensure other tips disappear during the transition.
+      wake = (duration) =>
+        duration ?= @animations.show.duration
+        updateBeforeWake()
         @$tip.stop().fadeIn duration, =>
-          if triggerChanged
-            onWake() if onWake?
-          if @safeToggle is on then @$tip.siblings(@classNames.tip).fadeOut()
-          @afterShow triggerChanged, event
-          @_setState 'awake'
+          @_setState 'awake', event # Hook in custom logic.
+          deferred.resolve()
+        @$tip.siblings(@classNames.tip).stop().fadeOut duration
       # - Wake up depending on current state. If we are in the middle of
       #   sleeping, stop sleeping by updating `_sleepCountdown` and wake up
       #   sooner.
       if @_state is 'sleeping'
         @debugLog 'clear sleep'
         clearTimeout @_sleepCountdown
-        duration = 0
-        wake()
+        wake(0)
       # - Start the normal wakeup and update `_wakeCountdown`.
       else if event? and event.type is 'truemouseenter'
-        triggerChanged = yes
         @_setState 'waking'
-        @_wakeCountdown = setTimeout wake, delay
-      yes
+        @_wakeCountdown = setTimeout wake, @animations.show.delay
+
+      promise

     # 𝒇 `sleepByTrigger` is a much simpler toggler compared to its counterpart
-    # `wakeByTrigger`. It also updates `_state` and returns a bool for success.
-    # As long as the tip isn't truly visible, or sleeping is redundant, it bails.
+    # `wakeByTrigger`. It also updates `_state` and returns a promise that
+    # fails if waking gets skipped. As long as the tip isn't truly visible, or
+    # sleeping is redundant, it bails.
     sleepByTrigger: ($trigger) ->
-      return no if @_state in ['asleep', 'sleeping']
+      deferred = $.Deferred()
+      promise = deferred.promise()
+
+      # - Don't toggle if asleep or sleeping, or if event isn't `truemouseleave`.
+      if @_state in ['asleep', 'sleeping']
+        deferred.reject()
+        return promise
+
       @_setState 'sleeping'
-      clearTimeout @_wakeCountdown
       @_sleepCountdown = setTimeout =>
-        @onHide()
+        @onHide() # Hook in custom logic.
         @$tip.stop().fadeOut @animations.hide.duration, =>
           @_setState 'asleep'
-          @afterHide()
+          deferred.resolve()
       , @animations.hide.delay
-      yes
+
+      promise

     # ### Content

@@ -677,15 +698,14 @@

     # 𝒇 Implement `onShow` and `afterShow` delegate methods such that they make
     # the tip invisible while it's being positioned and then reveal it.
-    onShow: (triggerChanged, event) ->
-      @$tip.css 'visibility', 'hidden' if triggerChanged is yes
+    onShow: (event) ->
+      return unless @_triggerChanged is yes
+      @$tip.css 'visibility', 'hidden'

-    afterShow: (triggerChanged, event) ->
-      if triggerChanged is yes
-        @$tip.css 'visibility', 'visible'
-        @_offsetStart =
-          top: event.pageY
-          left: event.pageX
+    afterShow: (event) ->
+      return unless @_triggerChanged is yes
+      @$tip.css 'visibility', 'visible'
+      @_offsetStart = { top: event.pageY, left: event.pageX }

     # 𝒇 Implement `offsetOnTriggerMouseMove` delegate method as the main snapping
     # positioning handler. Instead of returning false, we return our custom,