Move default root path logic to above to fix ws issues with managed vms

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -394,13 +394,11 @@
     panel        = @workspace.getView()
     filesPane    = panel.getPaneByName 'filesPane'

-    path = @workspaceData?.rootPath
-
+    path  = @workspaceData?.rootPath
+    path ?= '/root'  if machineData.isManaged()
     path ?= if owner = machineData.getOwner()
     then "/home/#{owner}"
     else '/'
-
-    path = '/root'  if machineData.isManaged()

     @workspace.ready ->
       filesPane.emit 'MachineMountRequested', machineData, path


IDE: Null check for empty string.

IDE: Null check for empty string.

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -1248,7 +1248,7 @@

           {content, path} = context.file

-          return  unless content
+          return  unless content?

           string = @rtm.getFromModel path
           string.setText content  if string


Test/MyTeam change seePreviouslyVisitedTeams function

--- client/test/lib/teams/teams.coffee
+++ client/test/lib/teams/teams.coffee
@@ -42,18 +42,27 @@
     teamsHelpers.createTeam(browser, user, inviteLink)
     teamsHelpers.createTeam(browser, user, createLink, 'AlreadyRegisteredUserName')

+
   signInWithNotAllowedEmail: (browser) ->
-    teamsHelpers.loginTeam(browser, user, yes , 'NotAllowedEmail')
+    teamsHelpers.loginTeam browser, user, yes , 'NotAllowedEmail', ->
+      browser.pause 1

+
   signInWithInvalidUsername: (browser) ->
-    teamsHelpers.loginTeam(browser, user, yes, 'InvalidUserName')
+    teamsHelpers.loginTeam browser, user, yes, 'InvalidUserName', ->
+      browser.pause 1

+
   signInWithInvalidPassword: (browser) ->
-    teamsHelpers.loginTeam(browser, user, yes, 'InvalidPassword')
+    teamsHelpers.loginTeam browser, user, yes, 'InvalidPassword', ->
+      browser.pause 1

+
   loginTeam: (browser) ->
-    teamsHelpers.loginTeam(browser, user, no)
+    teamsHelpers.loginTeam browser, user, no, ->
+      browser.pause 1

+
   seePreviouslyVisitedTeams: (browser) ->
     url = helpers.getUrl()
     teamsHelpers.loginTeam browser, user, no, '', ->


Use setDefault as setPersistentDefault doesn't seem to be working.

--- client/navbar/navbar.coffee
+++ client/navbar/navbar.coffee
@@ -1,6 +1,6 @@
-Session.setDefaultPersistent 'view', 'view-galicaster'
-Session.setDefaultPersistent 'zoom', 3
-Session.setDefaultPersistent 'showVumeter', false
+Session.setDefault 'view', 'view-galicaster'
+Session.setDefault 'zoom', 3
+Session.setDefault 'showVumeter', false
 Session.set 'search-query'

 minZoom = 1


Refactor auto-compile trigger logic

Refactor auto-compile trigger logic

--- services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
+++ services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
@@ -5,8 +5,12 @@
 	"libs/bib-log-parser"
 	"services/log-hints-feedback"
 ], (App, Ace, HumanReadableLogs, BibLogParser) ->
-	AUTO_COMPILE_TIMEOUT = 5000
-	OP_ACKNOWLEDGEMENT_TIMEOUT = 1100
+	AUTO_COMPILE_MAX_WAIT = 5000
+	# We add a 1 second debounce to sending user changes to server if they aren't
+	# collaborating with anyone. This needs to be higher than that, and allow for
+	# client to server latency, otherwise we compile before the op reaches the server
+	# and then again on ack.
+	AUTO_COMPILE_DEBOUNCE = 2000

 	App.controller "PdfController", ($scope, $http, ide, $modal, synctex, event_tracking, logHintsFeedback, localStorage) ->
 		# enable per-user containers by default
@@ -75,59 +79,96 @@
 			$scope.pdf.view = 'errors'
 			$scope.pdf.renderingError = true

-		autoCompileTimeout = null
+		autoCompileInterval = null
+		autoCompileIfReady = () ->
+			if $scope.pdf.compiling
+				return
+
+			# Only checking linting if syntaxValidation is on and visible to the user
+			autoCompileLintingError = ide.$scope.hasLintingError and ide.$scope.settings.syntaxValidation
+			if $scope.autoCompileLintingError != autoCompileLintingError
+				$scope.$apply () ->
+					$scope.autoCompileLintingError = autoCompileLintingError
+					# We've likely been waiting a while until the user fixed the linting, but we
+					# don't want to compile as soon as it is fixed, so reset the timeout.
+					$scope.startedTryingAutoCompileAt = Date.now()
+					$scope.docLastChangedAt = Date.now()
+			if autoCompileLintingError
+				return
+
+			# If there's a longish compile, don't compile immediately after if user is still typing
+			startedTryingAt = Math.max($scope.startedTryingAutoCompileAt, $scope.lastFinishedCompileAt || 0)
+
+			timeSinceStartedTrying = Date.now() - startedTryingAt
+			timeSinceLastChange = Date.now() - $scope.docLastChangedAt
+
+			shouldCompile = false
+			if timeSinceLastChange > AUTO_COMPILE_DEBOUNCE # Don't compile in the middle of the user typing
+				shouldCompile = true
+			else if timeSinceStartedTrying > AUTO_COMPILE_MAX_WAIT # Unless they type for a long time
+				shouldCompile = true
+			else if timeSinceStartedTrying < 0 or timeSinceLastChange < 0
+				# If time is non-monotonic, assume that the user's system clock has been
+				# changed and continue with compile
+				shouldCompile = true
+
+			if shouldCompile
+				triggerAutoCompile()
+
 		triggerAutoCompile = () ->
-			return if autoCompileTimeout or $scope.ui.pdfHidden
-
-			timeSinceLastCompile = Date.now() - $scope.recompiledAt
-			# If time is non-monotonic, assume that the user's system clock has been
-			# changed and continue with recompile
-			isTimeNonMonotonic = timeSinceLastCompile < 0
-
-			if isTimeNonMonotonic || timeSinceLastCompile >= AUTO_COMPILE_TIMEOUT
-				# If user has code check disabled, it is likely because they have
-				# linting errors that they are ignoring. Therefore it doesn't make sense
-				# to block auto compiles. It also causes problems where server-provided
-				# linting errors aren't cleared after typing
-				if (ide.$scope.settings.syntaxValidation and !ide.$scope.hasLintingError)
-					$scope.recompile(isAutoCompileOnChange: true) # compile if no linting errors
-				else if !ide.$scope.settings.syntaxValidation
-					$scope.recompile(isAutoCompileOnChange: true) # always recompile
-				else
-					$scope.$apply () ->
-						$scope.autoCompileLintingError = true
+			$scope.recompile(isAutoCompileOnChange: true)
+
+		startTryingAutoCompile = () ->
+			return if autoCompileInterval?
+			$scope.startedTryingAutoCompileAt = Date.now()
+			autoCompileInterval = setInterval autoCompileIfReady, 200
+
+		stopTryingAutoCompile = () ->
+			clearInterval autoCompileInterval
+			autoCompileInterval = null
+
+		$scope.$watch "uncompiledChanges", (uncompiledChanges) ->
+			if uncompiledChanges
+				startTryingAutoCompile()
 			else
-				# Extend remainder of timeout
-				autoCompileTimeout = setTimeout () ->
-					autoCompileTimeout = null
-					triggerAutoCompile()
-				, AUTO_COMPILE_TIMEOUT - timeSinceLastCompile
+				stopTryingAutoCompile()

 		$scope.uncompiledChanges = false
 		recalculateUncompiledChanges = () ->
-			if $scope.docLastChanged > $scope.projectLastCompiled
+			if $scope.ui.pdfHidden
+				# Don't both auto-compiling if pdf isn't visible
+				$scope.uncompiledChanges = false
+			else if !$scope.docLastChangedAt?
+				$scope.uncompiledChanges = false
+			else if !$scope.lastStartedCompileAt? or $scope.docLastChangedAt > $scope.lastStartedCompileAt
 				$scope.uncompiledChanges = true
 			else
 				$scope.uncompiledChanges = false

+		_updateDocLastChangedAt = () ->
+			$scope.docLastChangedAt = Date.now()
+			recalculateUncompiledChanges()
+
 		onDocChanged = () ->
 			$scope.autoCompileLintingError = false
-			$scope.docLastChanged = Date.now()
+			_updateDocLastChangedAt()
+
+		onDocSaved = () ->
+			# We use the save as a trigger too, to account for the delay between the client
+			# and server. Otherwise, we might have compiled after the user made
+			# the change on the client, but before the server had it.
+			_updateDocLastChangedAt()
+
+		onCompilingStateChanged = (compiling) ->
 			recalculateUncompiledChanges()
-
-		onCompilingStateChanged = (compiling) ->
-			if compiling
-				$scope.projectLastCompiled = Date.now()
-				recalculateUncompiledChanges()

 		autoCompileListeners = []
 		toggleAutoCompile = (enabling) ->
 			if enabling
 				autoCompileListeners = [
 					ide.$scope.$on "doc:changed", onDocChanged
-					ide.$scope.$on "doc:saved", onDocChanged
+					ide.$scope.$on "doc:saved", onDocSaved
 					$scope.$watch "pdf.compiling", onCompilingStateChanged
-					ide.$scope.$on "ide:opAcknowledged", _.debounce(triggerAutoCompile, OP_ACKNOWLEDGEMENT_TIMEOUT)
 				]
 			else
 				for unbind in autoCompileListeners
@@ -455,6 +496,7 @@

 			event_tracking.sendMBSampled "editor-recompile-sampled", options

+			$scope.lastStartedCompileAt = Date.now()
 			$scope.pdf.compiling = true
 			$scope.pdf.isAutoCompileOnLoad = options?.isAutoCompileOnLoad # initial autocompile

@@ -471,6 +513,8 @@
 			ide.$scope.$broadcast("flush-changes")

 			options.rootDocOverride_id = getRootDocOverride_id()
+
+			$scope.$apply()

 			sendCompileRequest(options)
 				.then (response) ->
@@ -487,7 +531,7 @@
 					$scope.pdf.error = true
 					$scope.pdf.view = 'errors'
 				.finally () ->
-					$scope.recompiledAt = Date.now()
+					$scope.lastFinishedCompileAt = Date.now()

 		# This needs to be public.
 		ide.$scope.recompile = $scope.recompile


config: main.dev: set guestuser's initial socialApiId as 0

we won't need that but in socialApi requester in bongo side requires to fetch
socialApiId of current user, which is guestuser for loggedout pages
cc/ @canthefason

--- config/main.dev.coffee
+++ config/main.dev.coffee
@@ -952,7 +952,7 @@
         go run ./go/src/socialapi/workers/migrator/main.go -c #{socialapi.configFilePath}

         # Required step for guestuser
-        mongo #{mongo} --eval='db.jAccounts.update({"profile.nickname":"guestuser"},{$set:{type:"unregistered"}});'
+        mongo #{mongo} --eval='db.jAccounts.update({"profile.nickname":"guestuser"},{$set:{type:"unregistered", socialApiId:0}});'

       }


activity: define initial options of `EditCommentForm` class

--- client/Social/Activity/views/comments/editcommentform.coffee
+++ client/Social/Activity/views/comments/editcommentform.coffee
@@ -1,6 +1,9 @@
 class EditCommentForm extends NewCommentForm

   constructor: (options = {}, data) ->
+
+    options.cssClass  = KD.utils.curry "edit-comment-box", options.cssClass
+    options.editable ?= yes

     super options, data


app: add a function to calculate channel link in teams context

--- client/app/lib/notifications/notificationlistitemview.coffee
+++ client/app/lib/notifications/notificationlistitemview.coffee
@@ -191,3 +191,9 @@

+calculateReactivityLink = (post) ->
+
+  channel = kd.singletons.socialapi.retrieveCachedItemById post.initialChannelId
+  return "/Channels/#{channel.name.toLowerCase()}/#{post.id}"
+
+


snapshots: AddSnapshot loader was not being hidden on error.

The snapshot loader now is hidden in the event of an error being
displayed to the user, to show that the process has been stopped.

--- client/app/lib/providers/machinesettingssnapshotsview.coffee
+++ client/app/lib/providers/machinesettingssnapshotsview.coffee
@@ -163,12 +163,14 @@
     machineId = machine._id
     label     = @addInputView.getValue()
     if not label? or label is ''
+      @addNewButton.hideLoader()
       return MachineSettingsSnapshotsView.notify \
         'Name length must be larger than zero'

     # Get the IDE view.
     openIdeByMachine machine, (err, ideController) =>
       if err
+        @addNewButton.hideLoader()
         @showNotification "Error, unable to create snapshot.", 'error'
         kd.error "Unable to create snapshot, IDE Could not be found", err
         return
@@ -176,6 +178,7 @@
       container = ideController?.getView()

       unless container?
+        @addNewButton.hideLoader()
         @showNotification "Error, unable to create snapshot.", 'error'
         return kd.error "Unable to create snapshot, IDE View could not be found"


fshelper: working version of group uploading, WIP

--- client/Finder/fs/fshelper.coffee
+++ client/Finder/fs/fshelper.coffee
@@ -213,15 +213,19 @@
       "#{KD.config.uploadsUri}/#{KD.whoami().getId()}/#{name}"

     upload : (name, content, type, slug, callback)->
-      vmController = KD.getSingleton 'vmController'
-      # TODO : change before merging
-      args = {name, bucket: "groups", "groupName", content}
-      vmController.run
+      # groups
+      args = {name, bucket : "groups", path : "buckets", content}
+
+      #user
+      # args = {name,  content}
+
+      KD.getSingleton('vmController').run
         method    : 's3.store'
         withArgs  : args
       , (err, res)->
-        if err then callback err
-        else callback null, FSHelper.s3.get name
+        console.log "upload", err, res
+        # if err then callback err
+        # else callback null, FSHelper.s3.get name

     remove : (name, callback)->
       vmController = KD.getSingleton 'vmController'


update product description for pupil_wusbc*

--- assets/coffeescript/product_list.coffee
+++ assets/coffeescript/product_list.coffee
@@ -38,11 +38,11 @@
     wusbc:
       id: 'wusbc'
       title_store: "usb-c mount"
-      description_store: "USB-C plug to connect to a wide range of cameras/sensors. Ships with standard tripod screw and mount for Intel RealSense D400 sensors. (**RealSense sensor not included.)"
+      description_store: "USB-C plug to connect to a wide range of cameras/sensors. Ships with standard tripod screw and mount. Compatible with Intel RealSense D400 series sensors. (*RealSense sensor not included.)"
       tech_specs:
         sensor: "No camera/sensor included."
       title_cart: "World Camera: USB-C mount"
-      description_cart: "USB-C plug to connect to a wide range of cameras/sensors. Ships with standard tripod screw and mount for Intel RealSense D400 sensors. (**RealSense ensor not included.)"
+      description_cart: "USB-C plug to connect to a wide range of cameras/sensors. Ships with standard tripod screw and mount. Compatible with Intel RealSense D400 series sensors. (*RealSense sensor not included.)"
       weight: 2
     # w3d:
     #   id: 'w3d'


Fix options passing.

Fix options passing.

--- src/coffee/retailer2master.coffee
+++ src/coffee/retailer2master.coffee
@@ -8,11 +8,18 @@
     throw new Error 'No base configuration in options!' unless options.baseConfig
     throw new Error 'No master configuration in options!' unless options.master
     throw new Error 'No retailer configuration in options!' unless options.retailer
-    super config: _.extend(options.master, options.baseConfig)
+
+    masterOpts = _.clone options.baseConfig
+    masterOpts.config = options.master
+    retialerOpts = _.clone options.baseConfig
+    retialerOpts.config = options.retailer
+
+    super masterOpts
+    @masterRest = @rest
+    @retailerRest = new Rest retialerOpts
+
     @logger = options.baseConfig.logConfig.logger
     @retailerProjectKey = options.retailer.project_key
-    @retailerRest = new Rest config: _.extend(options.retailer, options.baseConfig)
-    @masterRest = @rest

   run: (callback) ->
     Q.all([


Fixed: when a new access is created, the user is redirected to the balance view

--- client/app/views/new_bank.coffee
+++ client/app/views/new_bank.coffee
@@ -50,12 +50,15 @@
                 $("#add-bank-window").modal("hide")
                 button.removeClass "disabled"
                 button.html oldText
-
+
                 window.activeObjects.trigger "new_access_added_successfully", model

-                setTimeout 500, () ->
+                setTimeout () ->
                     $("#add-bank-window").modal("hide")
-
+                    router = window.app.router
+                    router.navigate '/', {trigger: true, replace: true}
+                , 500
+
             error: (model, xhr, options) ->
                 console.log "Error :" + xhr


fixed material specs for new code changes

--- spec/javascripts/jax/material_spec.js.coffee
+++ spec/javascripts/jax/material_spec.js.coffee
@@ -13,8 +13,8 @@
       class Jax.Material.TestLayer2 extends Jax.Material.Layer
         numPasses: -> 4
       matr = new Jax.Material
-      matr.addLayer layer1 = new Jax.Material.TestLayer1
-      matr.addLayer layer2 = new Jax.Material.TestLayer2
+      matr.addLayer layer1 = new Jax.Material.TestLayer1 {}, matr
+      matr.addLayer layer2 = new Jax.Material.TestLayer2 {}, matr

     it "should render in the expected order", ->
       layer1.setVariables = (context, mesh, model, vars, pass) -> layer1_order.push pass
@@ -27,7 +27,7 @@
     class Jax.Material.TestLayer extends Jax.Material.Layer
       setVariables: (context, mesh, model, vars, pass) -> return false
     matr = new Jax.Material
-    matr.addLayer new Jax.Material.TestLayer
+    matr.addLayer new Jax.Material.TestLayer {}, matr
     spyOn matr, 'drawBuffers'
     matr.render SPEC_CONTEXT, new Jax.Mesh.Triangles, new Jax.Model
     expect(matr.drawBuffers).not.toHaveBeenCalled()
@@ -36,7 +36,7 @@
     class Jax.Material.TestLayer extends Jax.Material.Layer
       setVariables: (context, mesh, model, vars, pass) -> return undefined
     matr = new Jax.Material
-    matr.addLayer new Jax.Material.TestLayer
+    matr.addLayer new Jax.Material.TestLayer {}, matr
     spyOn matr, 'drawBuffers'
     matr.render SPEC_CONTEXT, new Jax.Mesh.Triangles, new Jax.Model
     expect(matr.drawBuffers).toHaveBeenCalled()
@@ -45,7 +45,7 @@
     class Jax.Material.TestLayer extends Jax.Material.Layer
       setVariables: (context, mesh, model, vars, pass) -> return null
     matr = new Jax.Material
-    matr.addLayer new Jax.Material.TestLayer
+    matr.addLayer new Jax.Material.TestLayer {}, matr
     spyOn matr, 'drawBuffers'
     matr.render SPEC_CONTEXT, new Jax.Mesh.Triangles, new Jax.Model
     expect(matr.drawBuffers).toHaveBeenCalled()
@@ -53,7 +53,7 @@
   it "should not skip passes where setVariables is not defined", ->
     class Jax.Material.TestLayer extends Jax.Material.Layer
     matr = new Jax.Material
-    matr.addLayer new Jax.Material.TestLayer
+    matr.addLayer new Jax.Material.TestLayer {}, matr
     spyOn matr, 'drawBuffers'
     matr.render SPEC_CONTEXT, new Jax.Mesh.Triangles, new Jax.Model
     expect(matr.drawBuffers).toHaveBeenCalled()


Serialize query execution during hupped query

--- db.coffee
+++ db.coffee
@@ -67,6 +67,8 @@
 		return returnValue

 	db.runHuppedQuery = (sql, parameters..., callback) ->
+		do db.serialize
+
 		# 1st finalize all open queries
 		for queryID, query of queries
 			query.finalize()
@@ -77,6 +79,7 @@
 			# 3rd reprepare all queries that were previously open
 			for queryID, query of queries
 				query.hup()
+			do db.parallelize
 			callback?()

 		# 2nd run our hup’d query


Make start and end arrayports

Make start and end arrayports

--- src/components/ListenTouch.coffee
+++ src/components/ListenTouch.coffee
@@ -9,10 +9,10 @@
     @inPorts =
       element: new noflo.Port 'object'
     @outPorts =
-      start: new noflo.Port 'object'
+      start: new noflo.ArrayPort 'object'
       moveX: new noflo.Port 'number'
       moveY: new noflo.Port 'number'
-      end: new noflo.Port 'object'
+      end: new noflo.ArrayPort 'object'

     @inPorts.element.on 'data', (element) =>
       @subscribe element


Unescape document name (in case it contains #, ?, etc.)

For motif/company#5296.


Initial work on passing URN's as parameters and getting the latest version constructed.

--- src/js/collection-editor.coffee
+++ src/js/collection-editor.coffee
@@ -123,12 +123,27 @@
 # takes a callback function which gets passed the resulting URN
 construct_latest_urn = (callback) ->
   collection = $('#collection_select').val()
-  fusion_tables_query "SELECT ROWID FROM #{collection}", (data) =>
-    console.log data
-    last_available = if data['rows']? then data['rows'].length + 1 else 1
-    latest_urn = cite_urn($('#namespaceMapping').attr('value'),$('#collection_name').attr('value'),last_available,1)
-    console.log "Latest URN: #{latest_urn}"
-    callback(latest_urn)
+  urn_input = $('input[data-urn=true]')
+  if parse_query_string()[urn_input.attr('id')]?
+    urn_prefix = parse_query_string()[urn_input.attr('id')].split('.').slice(0,-1).join('.')
+    fusion_tables_query "SELECT COUNT() FROM #{collection} WHERE '#{urn_input.attr('id')}' STARTS WITH '#{urn_prefix}'", (data) =>
+      console.log data
+      if data['rows']?
+        existing_versions = parseInt(data['rows'][0][0])
+        latest_urn = "#{urn_prefix}.#{existing_versions + 1}"
+        console.log "Latest URN: #{latest_urn}"
+        callback(latest_urn)
+      else # invalid URN passed in, strip and retry
+        console.log "No existing versions for passed URN, constructing latest URN from scratch"
+        filter_url_params(parse_query_string(),[urn_input.attr('id')])
+        construct_latest_urn(callback)
+  else
+    fusion_tables_query "SELECT ROWID FROM #{collection}", (data) =>
+      console.log data
+      last_available = if data['rows']? then data['rows'].length + 1 else 1
+      latest_urn = cite_urn($('#namespaceMapping').attr('value'),$('#collection_name').attr('value'),last_available,1)
+      console.log "Latest URN: #{latest_urn}"
+      callback(latest_urn)

 scroll_to_bottom = ->
   $('html, body').animate({scrollTop: $(document).height()-$(window).height()},600,'linear')
@@ -177,7 +192,7 @@
   collection = $('#collection_select').val()
   for child in $('#collection_form').children()
     if $(child).attr('id')?
-      if parse_query_string()[$(child).attr('id')]?
+      if (parse_query_string()[$(child).attr('id')]?) && ($(child).attr('id') != $('input[data-urn=true]').attr('id'))
         $(child).val(parse_query_string()[$(child).attr('id')])
         filter_url_params(parse_query_string(),[$(child).attr('id')])
       else if localStorage["#{collection}:#{$(child).attr('id')}"]?


Forward window focus when active element is body

This problem was when the focus element is detached
when navigating away from the window and when
the window is then given focus again nothing has
focus.

The solution is to use RootView's focus handler
on the window to bring focus to the editor or
other view when the document's active element
is the body meaning nothing inside the RootView
currently has focus.

--- src/app/root-view.coffee
+++ src/app/root-view.coffee
@@ -55,20 +55,24 @@
     panesViewState: @panes.children().view()?.serialize()
     extensionStates: @serializeExtensions()

+  handleFocus: (e) ->
+    if @getActiveEditor()
+      @getActiveEditor().focus()
+      false
+    else
+      @setTitle(null)
+      focusableChild = this.find("[tabindex=-1]:visible:first")
+      if focusableChild.length
+        focusableChild.focus()
+        false
+      else
+        true
+
   handleEvents: ->
     @on 'toggle-dev-tools', => atom.toggleDevTools()
-    @on 'focus', (e) =>
-      if @getActiveEditor()
-        @getActiveEditor().focus()
-        false
-      else
-        @setTitle(null)
-        focusableChild = this.find("[tabindex=-1]:visible:first")
-        if focusableChild.length
-          focusableChild.focus()
-          false
-        else
-          true
+    @on 'focus', (e) => @handleFocus(e)
+    $(window).on 'focus', (e) =>
+      @handleFocus(e) if document.activeElement is document.body

     @on 'active-editor-path-change', (e, path) =>
       @project.setPath(path) unless @project.getRootDirectory()


Use 'find . -name .... -type f' to skip symlinks

--- lib/cscope.coffee
+++ lib/cscope.coffee
@@ -13,7 +13,8 @@
       args = ['/b/a/s'].concat(exts)
     else
       cmd = 'find'
-      args = [].concat.apply(['.', '-name', '*' + exts.shift()], ['-o', '-name', '*' + ext] for ext in exts)
+      args = [].concat.apply(['.', '\(', '-name', '*' + exts.shift()], ['-o', '-name', '*' + ext] for ext in exts)
+      args = args.concat.apply(args, ['\)', '-type', 'f'])
     return @runCommand cmd, args, {cwd: project, detached: true, stdio: ['ignore', out, 'pipe']}

   generateCscopeDB: (project) ->


:ok_hand: fix condition order

:ok_hand: fix condition order

--- src/lib/models-generator.coffee
+++ src/lib/models-generator.coffee
@@ -104,14 +104,13 @@
         jsonFilePath = normalize "#{@destinationDir}/#{modelName}.json"
         jsFilePath = normalize "#{@destinationDir}/#{modelName}.js"

-        if modelDefinition instanceof ModelDefinition
-            jsonContent = modelDefinition.toStringifiedJSON()
-        else
+        if modelDefinition not instanceof ModelDefinition
             jsonContent = JSON.stringify(modelDefinition, null, 2)
             fs.writeFileSync(jsonFilePath, jsonContent)
             fs.writeFileSync(jsFilePath, @getEmptyJSContent())
             return modelName

+        jsonContent = modelDefinition.toStringifiedJSON()
         fs.writeFileSync(jsonFilePath, jsonContent)
         fs.writeFileSync(jsFilePath, @getJSContent(modelDefinition.definition.validations))

@@ -132,6 +131,9 @@
     @private
     ###
     getJSContent: (validations) ->
+        if not validations
+            return @getEmptyJSContent()
+
         validateMethods = []
         for validation in validations
             for prop, rules of validation


fix box counter

fix box counter

--- server/models/message.coffee
+++ server/models/message.coffee
@@ -68,7 +68,7 @@

             callback null,
                 messages: messages
-                count: count[0]?.value or 0
+                count: count
                 conversationLengths: lengths

 Message.getResults = (mailboxID, params, callback) ->


fixed typo

fixed typo

--- src/scripts/githooks/coreos_restart/status.coffee
+++ src/scripts/githooks/coreos_restart/status.coffee
@@ -18,7 +18,7 @@
   # Parse the output for structured data.
   stdout = stdout.split "\n"
   for i in [0..(stdout.length - 1)]
-    if data[i] == "UNIT\tMACHINE\tACTIVE\tSUB"
+    if stdout[i] == "UNIT\tMACHINE\tACTIVE\tSUB"
       data = stdout[(i + 1)..]
       break


Complete Remote._ tests

Complete Remote._ tests

--- src/tests/spec/public/models/remote._.spec.coffee
+++ src/tests/spec/public/models/remote._.spec.coffee
@@ -64,24 +64,6 @@
 				remote.trigger('message', message.serialize())
 				expect(Remote.hashes.length).toBe(1)

-			it 'should limit the hashtable size to 1000', ->
-				to = fakeController.id
-				from = fakeController.id + '1'
-
-				expect(Remote.hashes.length).toBe(0)
-				for i in [0...1000]
-					message = new Message(to, from, 'testEvent', i)
-
-					# Trigger twice, hash is the same, so should only be processed once
-					remote.trigger('message', message.serialize())
-
-				expect(Remote.hashes.length).toBe(1000)
-
-				# The next message should splice of 200 hashes and add this new one
-				message = new Message(to, from, 'testEvent', 1000)
-				remote.trigger('message', message.serialize())
-				expect(Remote.hashes.length).toBe(801)
-
 			it 'should trigger the event from the message on our instance if `to` is our controller', ->
 				to = fakeController.id
 				from = fakeController.id + '1'
@@ -158,4 +140,68 @@
 				remote.send(message)
 				remote.send(message)
 				expect(Remote.hashes.length).toBe(1)
-				expect(remote._send.callCount).toBe(2)+				expect(remote._send.callCount).toBe(2)
+
+		describe 'when querying', ->
+
+			it 'should create a one-time callback for the result and emit the request to the target', ->
+				spyOn(remote, 'emit')
+
+				called = 0
+				remote.query('testQuery', 1, ->
+						called++
+					)
+
+				expect(remote.emit).toHaveBeenCalled()
+				callArgs = remote.emit.mostRecentCall.args
+				expect(callArgs[1]).toBe('testQuery')
+				queryID = callArgs[2]
+				expect(callArgs[3]).toBe(1)
+				expect(callArgs.length).toBe(4)
+
+				remote.trigger(queryID)
+				remote.trigger(queryID)
+				expect(called).toBe(1)
+
+		describe 'when queryied', ->
+
+			it 'should query the controller for the value and send it back to the node querying', ->
+				message = {
+					from: '1'
+				}
+				fakeController.query.andReturn(true)
+				spyOn(remote, 'emitTo')
+
+				remote._onQuery('testQuery', 'query1', 1, message)
+				callArgs = fakeController.query.mostRecentCall.args
+				expect(callArgs[0]).toBe('testQuery')
+				expect(callArgs[1]).toBe(1)
+
+				callArgs = remote.emitTo.mostRecentCall.args
+				expect(callArgs[0]).toBe(message.from)
+				expect(callArgs[1]).toBe('query1')
+				expect(callArgs[2]).toBe(true)
+
+		describe 'when adding a hash', ->
+
+			it 'should test if the hash is already cached and add it', ->
+				expect(remote._addHash(1)).toBe(true)
+				expect(remote._addHash(1)).toBe(false)
+				expect(remote._addHash(2)).toBe(true)
+
+
+
+			it 'should limit the hashtable size to 1000', ->
+				for i in [0...1000]
+					remote._addHash(i)
+
+				expect(Remote.hashes.length).toBe(1000)
+				remote._addHash(1000)
+				expect(Remote.hashes.length).toBe(801)
+
+		describe 'when pinged', ->
+
+			it 'should send a ping query', ->
+				spyOn(remote, 'query')
+				remote.ping()
+				remote.query.toHaveBeenCalled()


cleanup further

cleanup further

--- lib/motion.coffee
+++ lib/motion.coffee
@@ -45,7 +45,7 @@
   constructor: ->
     super

-    # visual mode can overwrite default wise and inclusiveness
+    # visual mode can overwrite default wise
     if @vimState.mode is 'visual'
       @inclusive = true
       @wise = @vimState.submode
@@ -100,10 +100,12 @@
   select: ->
     for selection in @editor.getSelections()
       succeeded = @selectByMotion(selection)
-      if (succeeded or @isMode('visual') or @is('CurrentSelection')) and
-          (@isInclusive() or @isLinewise()) and
-          @wise isnt 'blockwise'
-
+      continue if @wise is 'blockwise'
+
+      if @isMode('visual') or @is('CurrentSelection') # guaranteed to be @inclusive = true
+        swrap(selection).translateSelectionEndAndClip('forward')
+
+      else if succeeded and (@isInclusive() or @isLinewise())
         swrap(selection).translateSelectionEndAndClip('forward')

     @editor.mergeCursors()
@@ -128,10 +130,11 @@
       when 'blockwise'
         @vimState.selectBlockwise()

+  # return status
   selectByMotion: (selection) ->
     selection.modifySelection =>
       @moveWithSaveJump(selection.cursor)
-    @moveSucceeded ? not selection.isEmpty()
+    return @moveSucceeded ? not selection.isEmpty()

   setCursorBuffeRow: (cursor, row, options) ->
     if @isVerticalMotion() and @getConfig('moveToFirstCharacterOnVerticalMotion')


initial commit for new branch

initial commit for new branch

--- lib/atom-python-test-view.coffee
+++ lib/atom-python-test-view.coffee
@@ -17,6 +17,7 @@
       @panel ?= atom.workspace.addBottomPanel(item: this)
       @panel.hide()

+    # TODO: edit this part with HTML to make the output colored
     addLine: (line) ->
       @message += line
       @find(".output").text(@message)


Removed unused methods that were generating base string, signature and sorting

--- lib/oauth1.coffee
+++ lib/oauth1.coffee
@@ -29,22 +29,6 @@

 OAuth1ResponseParser = require './oauth1-response-parser'
 short_formats = OAuth1ResponseParser.short_formats
-
-ksort = (w) ->
-	r = {}
-	r[k] = w[k] for k in Object.keys(w).sort()
-	return r
-
-build_auth_string = (authparams) ->
-	"OAuth " + (k + '="' + v + '"' for k,v of ksort authparams).join ","
-
-sign_hmac_sha1 = (method, baseurl, secret, parameters) ->
-	data = method + '&' + (encodeURIComponent baseurl) + '&'
-	data += encodeURIComponent (k + '=' + v for k,v of ksort parameters).join '&'
-
-	hmacsha1 = crypto.createHmac "sha1", secret
-	hmacsha1.update data
-	hmacsha1.digest "base64"

 replace_param = (param, params, hard_params, keyset) ->
 	param = param.replace /\{\{(.*?)\}\}/g, (match, val) ->


Quick fix for #18

Quick fix for #18

--- lib/ide-haskell-repl-view.coffee
+++ lib/ide-haskell-repl-view.coffee
@@ -69,7 +69,7 @@
     commandPath = atom.config.get 'ide-haskell-repl.commandPath'
     commandArgs = atom.config.get 'ide-haskell-repl.commandArgs'

-    if commandPath.endsWith 'stack'
+    if comp? and commandPath.endsWith 'stack'
       if comp.startsWith 'lib:'
         comp = 'lib'
       comp = "#{cabal.name}:#{comp}"


Handle deserialize file path based preview

Previously there was no guard against a null atom.workspace
global during deserialization.

--- lib/markdown-preview-view.coffee
+++ lib/markdown-preview-view.coffee
@@ -20,9 +20,11 @@
     if @editorId?
       @resolveEditor(@editorId)
     else
-      @file = new File(filePath)
-      @handleEvents()
-      @renderMarkdown()
+      if atom.workspace?
+        @subscribeToFilePath(filePath)
+      else
+        @subscribe atom.packages.once 'activated', =>
+          @subscribeToFilePath(filePath)

   serialize: ->
     deserializer: 'MarkdownPreviewView'
@@ -31,6 +33,12 @@

   destroy: ->
     @unsubscribe()
+
+  subscribeToFilePath: (filePath) ->
+    @file = new File(filePath)
+    @trigger 'title-changed'
+    @handleEvents()
+    @renderMarkdown()

   resolveEditor: (editorId) ->
     resolve = =>
@@ -47,7 +55,7 @@
     if atom.workspace?
       resolve()
     else
-      atom.packages.once 'activated', =>
+      @subscribe atom.packages.once 'activated', =>
         resolve()
         @renderMarkdown()


Update parse_tex_log w some ad'l file heuristics

These were added to the ST version and are useful in some circumstances

--- lib/parsers/parse-tex-log.coffee
+++ lib/parsers/parse-tex-log.coffee
@@ -138,6 +138,8 @@
   file_useless1_rx = /\{\"?(?:\.|\.\.\/)*[^\.]+\.[^\{\}]*\"?\}(.*)/
   # Useless file #2: <filename.ext>; capture subsequent text
   file_useless2_rx = /<\"?(?:\.|\.\.\/)*[^\.]+\.[^>]*\"?>(.*)/
+  # attempt to filter out package continuation messages
+  file_package_rx = /^\s*\([a-zA-Z]+\)\s{4,}.+/
   pagenum_begin_rx = /\s*\[\d*(.*)/
   line_rx = /^l\.(\d+)\s(.*)/    # l.nn <text>
   warning_rx = /^(.*?) Warning: (.+)/ # Warnings, first line
@@ -231,6 +233,11 @@
       # HEURISTIC: check first if we just have a long "(.../file.tex" (or similar) line
       # A bit inefficient as we duplicate some of the code below for filename matching
       file_match = file_rx.exec(line)
+      if file_match
+        if line.startsWith('runsystem') or file_package_rx.match(line)
+          debug("Ignoring possible file: #{line}")
+          file_match = false
+
       if file_match
         debug("MATCHED (long line)")
         file_name = file_match[1]
@@ -271,7 +278,12 @@
           # HEURISTIC: if extra line begins with "Package:" "File:" "Document Class:",
           # or other "well-known markers",
           # we just had a long file name, so do not add
-          if extralen>0 && (extra.slice(0,5)=="File:" || extra.slice(0,8)=="Package:" || extra.slice(0,15)=="Document Class:") || (extra.slice(0,9)=="LaTeX2e <") || extra.match(assignment_rx)
+          if extralen > 0 and (
+            extra.slice(0,5) == "File:" or
+            extra.slice(0,8) == "Package:" or
+            extra.slice(0,11) == "Dictionary:" or
+            extra.slice(0,15) == "Document Class:"
+          ) or extra.slice(0,9) == "LaTeX2e <" or extra.match(assignment_rx)
             debug("Found File: and friends, or LaTeX2e, or assignment_rx match")
             extend_line = false
             # no need to recycle extra, as it's nothing we are interested in
@@ -283,6 +295,15 @@
             debug("Found [...]")
             extend_line = false
             recycle_extra = true # make sure we process the "l.<nn>" line!
+          # if "extra" (next line) starts with a ( and we already have a valid
+          # file, this is likely the start of something else we need to process
+          # as a file, so add a space
+          else if extralen > 0 and extra[0] == '(' and isfile(file_name)
+            line = "#{line} #{extra}"
+            debug("Extended #{line}")
+            linelen += extralen + 1
+            if extralen < 79
+              extend_line = false
           else
             line += extra
             debug("Extended: " + line)
@@ -341,7 +362,12 @@
       debug(line)

     # Skip things that are clearly not file names, though they may trigger false positives
-    if line.length>0 && (line.slice(0,5)=="File:" || line.slice(0,8)=="Package:" || line.slice(0,15)=="Document Class:") || (line.slice(0,9)=="LaTeX2e <")
+    if line.length > 0 and (
+      line.slice(0,5) == "File:" or
+      line.slice(0,8) == "Package:" or
+      line.slice(0,11) == "Dictionary:" or
+      line.slice(0,15) == "Document Class:"
+    ) or line.slice(0,9) == "LaTeX2e <" or line.match(assignment_rx)
       continue

     # Are we done? Get rid of extra spaces, just in case (we may have extended a line, etc.)


Add a PHP grammar definition to the grammarMap

--- lib/script.coffee
+++ lib/script.coffee
@@ -17,6 +17,9 @@
   Ruby:
     interpreter: "ruby"
     makeargs: (code) -> ['-e', code]
+  PHP:
+    interpreter: "php"
+    makeargs: (code) -> ['-r', code]

 module.exports =


requeue is neccesary but just once

requeue is neccesary but just once

--- lib/server.coffee
+++ lib/server.coffee
@@ -109,6 +109,7 @@
 					jumpon++
 					status_c = {}
 					tokill = []
+					resentdocs = false
 					for act in workque
 						switch act._status
 							when "new"
@@ -147,6 +148,15 @@
 								if act._otherhost?
 									status_c["duplicates"] ||= 0
 									status_c["duplicates"] += Object.keys(act._otherhost).length
+							when "fetching"
+								if getDocsCargo.length() is 0 and !getDocsCargo.running() and !resentdocs # nothing in the cargo.. but on the wue, re-queing ALL
+									resentdocs = true
+									console.log "No pending cargo for doc retrieval and current status is fetching..."
+									for item in workque
+										if item._status is "fetching"
+											item._status = "new"
+											item._lastchange = ~~((new Date).getTime() / 1000)
+
 						status_c[act._status] ||= 0
 						status_c[act._status]++
 					if tokill.length > 0


fixed missing "view" errors under windows

changed condition under which errors from calling the viewer are shown such that a missing executable throws an error on the user console

--- lib/viewer.coffee
+++ lib/viewer.coffee
@@ -20,7 +20,7 @@
     @ltConsole.addContent("Executing " + command, br = true)

     exec command, {}, (err, stdout, stderr) =>
-      if err > 1 # weirdness
+      if err # weirdness
         @ltConsole.addContent("ERROR #{err.code}: ", br=true)
         @ltConsole.addContent(line, br=true) for line in stderr.split('\n')


proper align for block

proper align for block

--- line_chart.coffee
+++ line_chart.coffee
@@ -44,6 +44,6 @@
     if @myChart && data.datasets
       for i in [0..@myChart.datasets.length - 1]
         for j in [0..@myChart.datasets[i].points.length - 1]
-           @myChart.datasets[i].points[j].value = data.datasets[i].data[j]
+          @myChart.datasets[i].points[j].value = data.datasets[i].data[j]

       @myChart.update()


Log error when trigger url returns 500

Log error when trigger url returns 500

--- packages/rocketchat-integrations/server/triggers.coffee
+++ packages/rocketchat-integrations/server/triggers.coffee
@@ -52,6 +52,11 @@
 		if not result? or result.statusCode isnt 200
 			if result.statusCode is 410
 				RocketChat.models.Integrations.remove _id: trigger._id
+				return
+
+			if result.statusCode is 500
+				console.log 'Request Error [500]', url
+				console.log result.content
 				return

 			if tries <= 6


Allow processOutgoingResponseScript to process erros

--- packages/rocketchat-integrations/server/triggers.coffee
+++ packages/rocketchat-integrations/server/triggers.coffee
@@ -123,7 +123,28 @@
 		return

 	HTTP.call opts.method, opts.url, opts, (error, result) ->
-		if not result? or result.statusCode isnt 200
+		scriptResult = undefined
+		if trigger.processOutgoingResponseScript? and trigger.processOutgoingResponseScript.trim() isnt ''
+			sandbox =
+				request: opts
+				response:
+					error: error
+					status_code: result.statusCode
+					content: result.data
+					content_raw: result.content
+					headers: result.headers
+				store: trigger.store
+
+			scriptResult = executeScript trigger.processOutgoingResponseScript, 'processOutgoingResponseScript', sandbox
+
+			if scriptResult?.content
+				sendMessage scriptResult.content
+				return
+
+			if scriptResult is false
+				return
+
+		if not result? or result.statusCode not in [200, 201, 202]
 			if error?
 				logger.outgoing.error error
 			if result?
@@ -143,23 +164,11 @@
 				Meteor.setTimeout ->
 					ExecuteTriggerUrl url, trigger, message, room, tries+1
 				, Math.pow(10, tries+2)
+
 			return

 		# process outgoing webhook response as a new message
-		else if result?.statusCode is 200
-			if trigger.processOutgoingResponseScript? and trigger.processOutgoingResponseScript.trim() isnt ''
-				sandbox =
-					request: opts
-					response:
-						content: result.data
-						content_raw: result.content
-						headers: result.headers
-					store: trigger.store
-
-				scriptResult = executeScript trigger.processOutgoingResponseScript, 'processOutgoingResponseScript', sandbox
-				result =
-					data: scriptResult?.content
-
+		if result?.statusCode in [200, 201, 202]
 			if result?.data?.text? or result?.data?.attachments?
 				sendMessage result.data


Fixes #3

Fixes #3

--- picoCTF-web/web/coffee/login.coffee
+++ picoCTF-web/web/coffee/login.coffee
@@ -11,7 +11,7 @@
         document.location.href = "problems.html"

 trigger_alert = (message)->
-  $("#error-alert").hide().text(message).show().delay(2000).fadeOut()
+  $("#error-alert").hide().text(message).show().delay(1500).fadeOut()

 login = (e) ->


Not showing popover in subcomments when not logged in.

--- core/app/backbone/views/sub_comments/sub_comment_view.coffee
+++ core/app/backbone/views/sub_comments/sub_comment_view.coffee
@@ -34,7 +34,7 @@
   onRender: -> @setPopover()

   setPopover: ->
-    if @model.can_destroy()
+    if Factlink.Global.signed_in && @model.can_destroy()
       popoverView = new SubCommentPopoverView
                           model: @model,
                           delete_message: 'Remove this comment'


use Array::splice

use Array::splice

--- src/buffer-stream.coffee
+++ src/buffer-stream.coffee
@@ -69,7 +69,7 @@
         for splitter in args
             i = @splitters.indexOf(splitter)
             continue if i is -1
-            @splitters = @splitters.slice(0,i).concat(@splitters.slice(i+1))
+            @splitters.splice(i, 1)
             break unless @splitters.length
         unless @splitters.length
             @enabled = off


Better error output on custom error objects

--- src/lib/docpad.coffee
+++ src/lib/docpad.coffee
@@ -1392,7 +1392,7 @@

 		# Log the error only if it hasn't been logged already
 		err.logged = true
-		err = new Error(err)  unless err instanceof Error
+		err = new Error(err)  unless err.message?
 		err.logged = true
 		docpad.log(type, locale.errorOccured, err.message, err.stack)


Added generate progress indicator. Refined middlewares.

--- src/lib/docpad.coffee
+++ src/lib/docpad.coffee
@@ -562,10 +562,14 @@
 		maxAge: 86400000

 		# Which middlewares would you like us to activate
+		# The standard middlewares (bodePArser, methodOverride, express router)
+		middlewareStandard: true
 		# The standard bodyParser middleware
-		middlewareBody: true
+		middlewareBodyParser: true
 		# The standard methodOverride middleware
-		middlewareOverride: true
+		middlewareMethodOverride: true
+		# The standard express router middleware
+		middlewareExpressRouter: true
 		# Our own 404 middleware
 		middleware404: true
 		# Our own 500 middleware
@@ -1955,8 +1959,11 @@
 				docpad.log 'debug', "Contextualized #{collection.length} files"
 				return next()

+		# Set progress indicator
+		opts.setProgressIndicator? -> ['contextualizeFiles',tasks.completed,tasks.total]
+
 		# Fetch
-		collection.forEach (file) -> tasks.push (complete) ->
+		collection.forEach (file,index) -> tasks.push (complete) ->
 			file.contextualize(complete)

 		# Start contextualizing
@@ -1989,6 +1996,9 @@
 				docpad.log 'debug', "Rendered #{collection.length} files"
 				return next()

+		# Set progress indicator
+		opts.setProgressIndicator? -> ['renderFiles',tasks.completed,tasks.total]
+
 		# Push the render tasks
 		collection.forEach (file) -> tasks.push (complete) ->
 			# Skip?
@@ -2034,8 +2044,11 @@
 				docpad.log 'debug', "Wrote #{collection.length} files"
 				return next()

+		# Set progress indicator
+		opts.setProgressIndicator? -> ['writeFiles',tasks.completed,tasks.total]
+
 		# Cycle
-		collection.forEach (file) -> tasks.push (complete) ->
+		collection.forEach (file,index) -> tasks.push (complete) ->
 			# Skip
 			dynamic = file.get('dynamic')
 			write = file.get('write')
@@ -2229,12 +2242,13 @@
 		docpad = @
 		templateData = opts.templateData or @getTemplateData()
 		collection = opts.collection or @getDatabase()
+		setProgressIndicator = opts.setProgressIndicator or null

 		# Contextualize the datbaase, perform two render passes, and perform a write
 		balUtil.flow(
 			object: docpad
 			action: 'contextualizeFiles renderFiles renderFiles writeFiles'
-			args: [{collection,templateData}]
+			args: [{collection,templateData,setProgressIndicator}]
 			next: (err) ->
 				return next(err)
 		)
@@ -2277,11 +2291,29 @@
 		docpad = @
 		docpad.lastGenerate ?= new Date('1970')

+		# Progress
+		progressIndicator = null
+		opts.setProgressIndicator = (_progressIndicator) ->
+			progressIndicator = _progressIndicator
+		showProgress = ->
+			progress = progressIndicator?()
+			if progress
+				[stage,completed,total] = progress
+				percent = Math.floor((completed/total)*100)+'%'
+				docpad.log 'info', "Currently on #{stage} at #{percent}"
+		progressInterval = setInterval(showProgress,10000)
+
+		# Finish
+		finish = (err) ->
+			clearInterval(progressInterval)
+			progressInterval = null
+			next(err)
+
 		# Re-load and re-render only what is necessary
 		if opts.reset? and opts.reset is false
 			# Prepare
 			docpad.generatePrepare (err) ->
-				return next(err)  if err
+				return finish(err)  if err
 				database = docpad.getDatabase()

 				# Create a colelction for the files to reload
@@ -2294,7 +2326,7 @@

 				# Perform the reload of the selected files
 				docpad.loadFiles {collection:filesToReload}, (err) ->
-					return next(err)  if err
+					return finish(err)  if err

 					# Create a collection for the files to render
 					filesToRender = opts.filesToRender or new FilesCollection()
@@ -2316,11 +2348,11 @@

 					# Perform the re-render of the selected files
 					docpad.generateRender {collection:filesToRender}, (err) ->
-						return next(err)  if err
+						return finish(err)  if err

 						# Finish up
 						docpad.generatePostpare {count:filesToRender.length}, (err) ->
-							return next(err)
+							return finish(err)

 		# Re-load and re-render everything
 		else
@@ -2330,7 +2362,7 @@
 				action: 'generatePrepare generateCheck generateClean generateParse generateRender generatePostpare'
 				args: [opts]
 				next: (err) ->
-					return next(err)
+					return finish(err)
 			)

 		# Chain
@@ -2724,15 +2756,15 @@
 		# Chain
 		@

-	# Server Middelware: Powering
-	serverMiddlewarePowering: (req,res,next) ->
+	# Server Middleware: Header
+	serverMiddlewareHeader: (req,res,next) ->
 		tools = res.get('X-Powered-By').split(/[,\s]+/g)
 		tools.push 'DocPad'
 		tools = tools.join(',')
 		res.set('X-Powered-By',tools)
 		next()

-	# Server Middelware: Router
+	# Server Middleware: Router
 	serverMiddlewareRouter: (req,res,next) =>
 		# Prepare
 		docpad = @
@@ -2750,13 +2782,12 @@
 		# if we aren't do a permanent redirect
 		url = document.get('url')
 		if url isnt pageUrl
-			console.log(url, url.indexOf?);
 			return res.redirect(301,url)

 		# Serve the document to the user
 		docpad.serveDocument({document,req,res,next})

-	# Server Middelware: 404
+	# Server Middleware: 404
 	serverMiddleware404: (req,res,next) =>
 		# Prepare
 		docpad = @
@@ -2769,7 +2800,7 @@
 		document = database.findOne(relativeOutPath: '404.html')
 		docpad.serveDocument({document,req,res,next,statusCode:404})

-	# Server Middelware: 404
+	# Server Middleware: 404
  	serverMiddleware500: (err,req,res,next) =>
 		# Prepare
 		docpad = @
@@ -2796,8 +2827,9 @@
 		serverHttp = null

 		# Config
-		opts.middlewareBody ?= config.middlewareBody
-		opts.middlewareOverride ?= config.middlewareOverride
+		opts.middlewareBodyParser ?= config.middlewareBodyParser ? config.middlewareStandard
+		opts.middlewareMethodOverride ?= config.middlewareMethodOverride ? config.middlewareStandard
+		opts.middlewareExpressRouter ?= config.middlewareExpressRouter ? config.middlewareStandard
 		opts.middleware404 ?= config.middleware404
 		opts.middleware500 ?= config.middleware500

@@ -2852,21 +2884,21 @@
 				express ?= require('express')

 				# POST Middleware
-				serverExpress.use(express.bodyParser())  if opts.middlewareBody isnt false
-				serverExpress.use(express.methodOverride())  if opts.middlewareOverride isnt false
-
-				# DocPad Header
-				serverExpress.use(docpad.serverMiddlewarePowering)
+				serverExpress.use(express.bodyParser())  if opts.middlewareBodyParser isnt false
+				serverExpress.use(express.methodOverride())  if opts.middlewareMethodOverride isnt false
+
+				# DocPad Header Middleware
+				serverExpress.use(docpad.serverMiddlewareHeader)
+
+				# Router Middleware
+				serverExpress.use(serverExpress.router)  if opts.middlewareExpressRouter isnt false

 				# Emit the serverExtend event
 				# So plugins can define their routes earlier than the DocPad routes
 				docpad.emitSync 'serverExtend', {server:serverExpress,serverExpress,serverHttp,express}, (err) ->
 					return next(err)  if err

-					# Router Middleware
-					serverExpress.use(serverExpress.router)
-
-					# Routing
+					# DocPad Router Middleware
 					serverExpress.use(docpad.serverMiddlewareRouter)

 					# Static
@@ -2875,10 +2907,10 @@
 					else
 						serverExpress.use(express.static(config.outPath))

-					# 404 Middleware
+					# DocPad 404 Middleware
 					serverExpress.use(docpad.serverMiddleware404)  if opts.middleware404 isnt false

-					# 500 Middleware
+					# DocPad 500 Middleware
 					serverExpress.error(docpad.serverMiddleware500)  if opts.middleware500 isnt false

 				# Start the Server


+ Factory.getCacheName static method

+ Factory.getCacheName static method

--- src/cache-factory.coffee
+++ src/cache-factory.coffee
@@ -1,5 +1,5 @@
 # Copyright (c) 2014-2015 Riceball LEE, MIT License
-path                  = require('path.js')
+path                  = require('path.js/lib/path').path
 customFactory         = require('custom-factory')
 injectMethods         = require('util-ex/lib/injectMethods')
 inherits              = require('inherits-ex/lib/inherits')
@@ -33,47 +33,45 @@
   registeredObjects = Factory._objects
   aliases = Factory._aliases

+  Factory.getCacheName = getCacheName = (aName, aOptions, cls)->
+    cached = aOptions.cached if aOptions?
+    cached = cached.name if isObject(cached) and cached.name?
+
+    if cached and not isString cached
+      # no cache name, so 'hash' it.
+      opts = extend {}, aOptions
+      delete opts.cached
+      delete opts.name
+      # encode as cache name.
+      cached = bytewise.encode(opts)
+
+    if isString(cached) and cached.length
+      if cached[0] isnt '/'
+        cls = Factory.registeredClass aName unless cls
+        cached = path.join(Factory.path(cls), cached)
+    cached
+
+
   getInstance = (aName, aOptions)->
     cached = aOptions.cached if aOptions?
     if cached?
-      cls = Factory[aName]
-      if cls is undefined
-        aName = Factory.getRealNameFromAlias aName
-        cls = Factory[aName] if aName
+      cls = Factory.registeredClass aName
       return unless cls
-      if cached is false
+      popped = cached.popped if cached.popped?
+      cachedName = getCacheName aName, aOptions, cls
+      if cachedName is false
         # createObject(Class, arg1, arg2) = new Class(arg1, arg2)
         result = createObject cls, undefined, aOptions
-      else
-        # the cache item name:
-        if isString cached
-          cachedName = cached
-          cached = undefined
-        else
-          popped = cached.popped
-          if cached.name?
-            cachedName = cached.name
-          else
-            opts = extend {}, aOptions
-            delete opts.cached
-            delete opts.name
-            #opts.path = Factory.path cls
-            #aOptions.name = aName unless aOptions.name?
-            cachedName = bytewise.encode(opts)
-        if isString(cachedName) and cachedName.length
-          if cachedName[0] isnt '/'
-            cachedName = path.join(Factory.path(cls), cachedName)
-          result = instanceCache.get(cachedName)
-          if result is undefined
-            result = createObject cls, undefined, aOptions
-            cached = undefined unless isObject cached
-            instanceCache.set cachedName, result, cached
-          else if popped
-            instanceCache.del(cachedName)
-        else
-          result = Factory._get(aName, aOptions)
-    else
-      result = Factory._get(aName, aOptions)
+      else if isString cachedName
+        result = instanceCache.get(cachedName)
+        if result is undefined
+          result = createObject cls, undefined, aOptions
+          cached = undefined unless isObject cached
+          instanceCache.set cachedName, result, cached
+        else if popped
+          instanceCache.del(cachedName)
+    result = Factory._get(aName, aOptions) unless result
     return result

+
   Factory.get = getInstance if getInstance isnt Factory.get


re-enable support for Node.js and AMD style exports

--- src/lib/expose.coffee
+++ src/lib/expose.coffee
@@ -12,16 +12,12 @@

   # Node.js
-  ###
   if module?.exports
     module.exports = lib
-  ###

   # AMD / RequireJS
-  ###
-  else if define?.amd
+  if define?.amd
     define [], -> lib
-  ###

   # windowed browser
   if typeof window is 'object'


Remove hex function, make inline.

Remove hex function, make inline.

--- src/lib/packet.coffee
+++ src/lib/packet.coffee
@@ -1,11 +1,6 @@
 parsePattern = require("./pattern").parse
 ieee754 = require "./ieee754"
 stream = require "stream"
-
-# Convert an array of bytes into an hex string, two characters for each byte.
-hex = (bytes) ->
-  h = bytes.map (b) -> if b < 10 then "0" + b.toString(16) else b.toString(16)
-  h.join("")

 # Default callback, when no callback is provided.
 noop = (value) -> value
@@ -286,7 +281,9 @@

         # Create a hex string.
         if part.type == "h"
-          value = hex bytes.reverse()
+          bytes.reverse()
+          hex = bytes.map (b) -> (b >> 4).toString(16) + (b & 0x0f).toString(16)
+          value = hex.join("")

         # Convert to float or double.
         else if part.type == "f"


match my custom attribute

match my custom attribute

--- src/coffee/import.coffee
+++ src/coffee/import.coffee
@@ -6,6 +6,7 @@
 CONS = require './constants'
 GLOBALS = require './globals'
 Validator = require './validator'
+QueryUtils = require './queryutils'

 # TODO:
 # - better organize subcommands / classes / helpers
@@ -74,7 +75,7 @@

   processProducts: (products) ->
     console.log "Mapping done. About to process existing product(s) ..."
-    @_mapMatchFunction(@matchBy)(@client.productProjections, products)
+    QueryUtils.mapMatchFunction(@matchBy)(@client.productProjections, products)
     .then (payload) =>
       existingProducts = payload.body.results
       console.log "Comparing against #{payload.body.total} existing product(s) ..."
@@ -90,36 +91,6 @@
       # TODO: resolve with a summary of the import
       console.log "Finished processing #{_.size result} product(s)"
       Promise.resolve result
-
-  _mapMatchFunction: (matchBy) ->
-    switch matchBy
-      when 'id' then @_matchById
-      when 'sku' then @_matchBySku
-      when 'slug' then @_matchBySlug
-
-  # Matches products by `id` attribute
-  # @param {object} service - SDK service object
-  # @param {Array} products
-  _matchById: (service, products) ->
-    ids = products.map((p) -> "\"#{p.product.id}\"")
-    service.staged().filter("id:in (#{ids.join(',')})").fetch()
-
-  # Matches products by `id` attribute
-  # @param {object} service - SDK service object
-  # @param {Array} products
-  _matchBySku: (service, products) ->
-    skus = _.flatten(products.map((p) ->
-      [p.product.masterVariant.sku].concat(p.product.variants.map((v) ->
-        v.sku))))
-    skuString = "sku in (\"#{skus.join('", "')}\")"
-    filterInput = "masterVariant(#{skuString}) or variants(#{skuString})"
-    service.staged().filter(filterInput).fetch()
-
-  _matchBySlug: (service, products) ->
-    slugs = products.map((p) ->
-      p.product.slug[GLOBALS.DEFAULT_LANGUAGE])
-    filterInput = "slug in (\"#{slugs.join('", "')}\")"
-    service.staged().filter(filterInput).fetch()

   changeState: (publish = true, remove = false, filterFunction) ->
     @publishProducts = true
@@ -207,10 +178,10 @@
           console.warn "Ignoring variant as no match by SKU found for: ", variant
     _.map productsToUpdate

-  getCustomAttributeValue: (variant) ->
+  getCustomAttributeValue: (variant, name) ->
     variant.attributes or= []
     attrib = _.find variant.attributes, (attribute) =>
-      attribute.name is @customAttributeNameToMatch
+      attribute.name is (@customAttributeNameToMatch unless name)
     attrib?.value

   match: (entry) ->


lodheatmap: clean up labels

lodheatmap: clean up labels

--- src/lodheatmap.coffee
+++ src/lodheatmap.coffee
@@ -27,13 +27,12 @@
                                  # also, optionally chrname, chrstart, chrend

         # for categorical scale
-        if data.ycat?
+        if data.ycat? # if data.ycat provided (categorical labels), data.y ignored
             data.y = (i+1 for i of data.ycat)
-        unless data.y?
+        unless data.y? # if neither data.ycat nor data.y provided, create labels
             data.ycat = (i+1 for i of data.lod[0])
             data.y = (i+1 for i of data.lod[0])
         data.y = (+yv for yv in data.y) # make sure it's numeric
-        data.ycat = data.y unless data.ycat?
         n_pos = data.chr.length
         n_lod = data.y.length

@@ -108,12 +107,15 @@
                         cells.push({lod: lod, chr:chr, pos:pos, posindex:+i, lodindex:+j})
         calc_chrcell_rect(cells, xmid_scaled, ymid_scaled)

+        # tool tips
         celltip = d3.tip()
                    .attr('class', "d3-tip #{tipclass}")
                    .html((d) ->
                              z = d3.format(".2f")(Math.abs(d.lod))
                              p = d3.format(".1f")(d.pos)
-                             "#{d.chr}@#{p}, #{data.ycat[d.lodindex]} → #{z}")
+                             lodlabel = if data.ycat? then data.ycat[d.lodindex] else formatAxis(data.y)(data.y[d.lodindex])
+                             return "#{lodlabel}, #{d.chr}@#{p} → #{z}" if horizontal
+                             "#{d.chr}@#{p}, #{lodlabel} → #{z}")
                    .direction(() ->
                        return 'n' if horizontal
                        'e')


added time zone to compiled notice

added time zone to compiled notice

--- html/index_html.coffee
+++ html/index_html.coffee
@@ -2,7 +2,7 @@
 	index-html.coffee: creates the index.html file
 ###

-#console.log '**** index_html compiling'
+console.log '**** index_html compiling'

 fs   = require 'fs'
 exec = require('child_process').exec
@@ -12,7 +12,7 @@
 		dk = require('../packages/drykup/drykup') expand: yes

 doctype 5
-html ->
+html ->
 	head ->
 		meta "http-equiv": "Content-Type", content:"text/html; charset=UTF-8"
 		title "FTN"
@@ -29,7 +29,7 @@
 		div x:"i=indexHdr", ->
 			div x:"i=indexLogo", 'FTN'
 			div {x:"i=indexCompiled"},
-				'(Compiled ' + (new Date().toLocaleString()[0..20]) + ')'
+				'(Compiled ' + (new Date().toLocaleString()[0..20]) + ' pacific)'

 		div x:"i=urlTabs", ->
 			div x:"i=urlTabRow"
@@ -42,4 +42,4 @@
 	#console.log _.trim stdout
 	if err or stderr then #console.log "ERROR: kanso push exec " + err + '\n' + stderr

-#console.log '**** index_html compiled'
+console.log '**** index_html compiled'


Update Atom config

Update Atom config

--- atom/config.cson
+++ atom/config.cson
@@ -85,6 +85,7 @@
     showErrorInline: false
     showErrorTabLine: true
     statusIconScope: "File"
+    errorPanelHeight: 142
   "line-ending-converter":
     showEolInStatusBar: false
   "linter-shellcheck": {}
@@ -103,3 +104,4 @@
   "markdown-preview":
     useGitHubStyle: true
   "svg-preview": {}
+  "expand-region": {}


Remove sinon from button view with menu

Remove sinon from button view with menu

--- test/components/buttons/buttonviewwithmenu.coffee
+++ test/components/buttons/buttonviewwithmenu.coffee
@@ -8,17 +8,14 @@
 describe 'KDButtonViewWithMenu', ->
   beforeEach ->
     @sinon = sinon.sandbox.create()
-    @addCallback = @sinon.spy (->)
-    @editCallback = @sinon.spy (->)
-    @deleteCallback = @sinon.spy (->)
     @view = new KDButtonViewWithMenu
       title : 'Settings'
       iconClass : 'settings'
       cssClass : 'settings-menu'
       menu :
-        'Add Into' : { callback: => @addCallback() }
-        'Edit' : { callback: => @editCallback() }
-        'Delete' : { callback: => @deleteCallback() }
+        'Add Into' : { callback: => (->) }
+        'Edit' : { callback: => (->) }
+        'Delete' : { callback: => (->) }

   afterEach ->
     @sinon.restore()


Add support for message with attachments

--- src/matteruser.coffee
+++ src/matteruser.coffee
@@ -1,6 +1,11 @@
 {Robot,Adapter,TextMessage,User,EnterMessage,LeaveMessage} = require 'hubot'

 MatterMostClient = require 'mattermost-client'
+
+class AttachmentMessage extends TextMessage
+
+    constructor: (@user, @text, @file_ids, @id) ->
+        super @user, @text, @id

 class Matteruser extends Adapter

@@ -177,7 +182,10 @@
           user.mm.dm_channel_id = mmPost.channel_id
         @robot.logger.debug 'Text: ' + text

-        @receive new TextMessage user, text, mmPost.id
+        if mmPost.file_ids?
+            @receive new AttachmentMessage user, text, mmPost.file_ids, mmPost.id
+        else
+            @receive new TextMessage user, text, mmPost.id
         @robot.logger.debug "Message sent to hubot brain."
         return true


Remove debug console logs

Remove debug console logs

--- sample.coffee
+++ sample.coffee
@@ -18,12 +18,9 @@
 myJobs = new JobCollection 'queue',
    idGeneration: 'MONGO'
    transform: (d) ->
-      console.log "Transforming...", d
       try
          res = myJobs.createJob d
-         console.warn "succeeded in transform"
       catch e
-         console.warn "failed in transform"
          res = d
       return res

@@ -455,7 +452,6 @@

       # Create a job to make a thumbnail for each newly uploaded image
       addedFileJob = (file) ->
-         console.warn "Added file!", file
          # Don't make new jobs for files tha already have them
          unless file?.metadata?._Job?
             outputFileId = myData.insert
@@ -479,7 +475,6 @@

       # If a removed file has an associated cancellable job, cancel it.
       removedFileJob = (file) ->
-         console.warn "Removed a file!", file._id
          if file.metadata?._Job
             if job = myJobs.findOne({_id: file.metadata._Job, status: { $in: myJobs.jobStatusCancellable }},{ fields: { log: 0 }})
                console.log "Cancelling the job for the removed file!", job._id
@@ -494,15 +489,12 @@
       # When a file's data changes, call the appropriate functions
       # for the removal of the old file and addition of the new.
       changedFileJob = (oldFile, newFile) ->
-         console.warn "Changed file!", oldFile._id
          if oldFile.md5 isnt newFile.md5
             if oldFile.metadata._Job?
                # Only call if this file has a job outstanding
                console.warn 'Outstanding job!'
                removedFileJob oldFile
             addedFileJob newFile
-         else
-            console.warn "File data didn't change"

       # Watch for changes to uploaded image files
       fileObserve = myData.find(


update

update

--- src/mkts-table.coffee
+++ src/mkts-table.coffee
@@ -367,7 +367,7 @@
 #===========================================================================================================
 # EVENT GENERATORS
 #-----------------------------------------------------------------------------------------------------------
-@_walk_events = ( me, selectors_and_content_events, layout_name_stack ) ->
+@_walk_events = ( me, selectors_and_content_events, layout_name_stack, field_selector_stack ) ->
   @_compute_cell_dimensions     me
   @_compute_field_dimensions    me
   @_compute_border_dimensions   me
@@ -377,7 +377,7 @@
   me._tmp_is_outermost  = layout_name_stack.length < 2
   me._tmp_name          = layout_name_stack.join '/'
   ### Preparatory ###
-  yield from @_walk_opening_events                      me
+  yield from @_walk_opening_events                      me, layout_name_stack, field_selector_stack
   yield from @_walk_style_events                        me ### TAINT should write to document preamble ###
   #.........................................................................................................
   ### Borders, content ###
@@ -407,7 +407,7 @@
   yield return

 #-----------------------------------------------------------------------------------------------------------
-@_walk_opening_events = ( me ) ->
+@_walk_opening_events = ( me, layout_name_stack, field_selector_stack ) ->
   @_ensure_unitvector me
   layout_name       = me.name
   unitwidth_txt     = UNITS.as_text me.unitwidth
@@ -727,6 +727,7 @@
   seen_fieldnrs           = new Set()
   selector                = @_resolve_aliases me, selector
   #.........................................................................................................
+  ### TAINT must resolve aliases ###
   for term in selector
     if CND.isa_text term
       for cell from IG.GRID.walk_cells_from_selector me.grid, selector


Null check not needed

Null check not needed

--- resources/assets/coffee/react/profile-page/links.coffee
+++ resources/assets/coffee/react/profile-page/links.coffee
@@ -163,7 +163,7 @@

     return unless value?

-    playsWith = (value ? []).map (s) ->
+    playsWith = value.map (s) ->
       osu.trans "common.device.#{s}"
     .join ', '


Fixed sidebar not animating sometimes

Fixed sidebar not animating sometimes

--- resources/assets/scripts/app/controllers/sidebar.coffee
+++ resources/assets/scripts/app/controllers/sidebar.coffee
@@ -15,8 +15,8 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.

 module.exports = angular.module('ponyfm').controller "sidebar", [
-    '$scope', '$modal', 'playlists', '$rootScope'
-    ($scope, $modal, playlists, $rootScope) ->
+    '$scope', '$modal', 'playlists', '$rootScope', '$timeout'
+    ($scope, $modal, playlists, $rootScope, $timeout) ->
         $scope.playlists = playlists.pinnedPlaylists
         $scope.menuVisible = false
         $scope.menuActive = false
@@ -24,13 +24,25 @@
         $scope.navStyle = {}

         $rootScope.$on('sidebarToggled', () ->
-            $scope.menuVisible = !$scope.menuVisible
-            $scope.menuActive = $scope.menuVisible
+            $timeout(() ->
+                if $scope.menuVisible
+                    $scope.navStyle.transform = ''
+                    $scope.menuAnimated = true
+
+                $scope.menuVisible = !$scope.menuVisible
+                $scope.menuActive = $scope.menuVisible
+
+                console.log $scope.menuActive
+            )
         )

         $rootScope.$on('sidebarHide', () ->
-            $scope.menuVisible = false
-            $scope.menuActive = false
+            $timeout(() ->
+                $scope.navStyle.transform = ''
+                $scope.menuAnimated = true
+                $scope.menuVisible = false
+                $scope.menuActive = false
+            )
         )

         $scope.createPlaylist = () ->
@@ -78,6 +90,7 @@
         touchingNav = false

         onStart = (e) ->
+
             if !$scope.menuVisible
                 return


Prototype.js passes an event object to a handler function for all browsers.

--- src/noreferrer.coffee
+++ src/noreferrer.coffee
@@ -30,26 +30,26 @@
       # Disable opening a link in a new window with middle click
       middlebutton = false

-      kill_href = (ev = window.event) ->
+      kill_href = (ev) ->
         a.href = 'javascript:void(0)'
         return

-      restore_href = (ev = window.event) ->
+      restore_href = (ev) ->
         a.href = href
         return

       Event.observe a, name, restore_href for name in ['mouseout', 'mouseover', 'focus', 'blur']

-      Event.observe a, 'mousedown', (ev = window.event) ->
+      Event.observe a, 'mousedown', (ev) ->
         if Event.isMiddleClick(ev)
           middlebutton = true
         return

-      Event.observe a, 'blur', (ev = window.event) ->
+      Event.observe a, 'blur', (ev) ->
         middlebutton = false
         return

-      Event.observe a, 'mouseup', (ev = window.event) ->
+      Event.observe a, 'mouseup', (ev) ->
         return unless Event.isMiddleClick(ev) && middlebutton
         kill_href()
         Event.stop(ev)
@@ -74,7 +74,7 @@
           """.replace(/>\s+/g, '>')

       if Prototype.Browser.IE
-        Event.observe a, 'click', (ev = window.event) ->
+        Event.observe a, 'click', (ev) ->
           switch target = @target || '_self'
             when '_self', window.name
               win = window
@@ -89,7 +89,7 @@
           false
       else
         uri = "data:text/html;charset=utf-8,#{encodeURIComponent(body)}"
-        Event.observe a, 'click', (ev = window.event) ->
+        Event.observe a, 'click', (ev) ->
           @href = uri
           true


fixed interface change in marionette

fixed interface change in marionette

--- core/app/backbone/views/fact_relations/fact_relations_view.coffee
+++ core/app/backbone/views/fact_relations/fact_relations_view.coffee
@@ -41,8 +41,8 @@
     else
       @itemViewForModel(item)

-  buildItemView: (item, itemView) ->
-    super item, @itemViewFor(item, itemView)
+  buildItemView: (item, itemView, options) ->
+    super item, @itemViewFor(item, itemView), options

 class window.FactRelationsView extends Backbone.Marionette.Layout
   className: "tab-content"


Added check to ensure URL is provided when downloading files.

--- src/Window.coffee
+++ src/Window.coffee
@@ -36,4 +36,6 @@
     variable = @GET(name)
     if variable == undefined then undefined else !!parseInt(variable)

-  downloadFile: (url) -> location.href = url
+  downloadFile: (url) ->
+    unless url then return Logger.error('Could not download file - no URL provided')
+    location.href = url


Refactor for clarity

Refactor for clarity

--- src/resources-packaged/ops/javascript/orbeon/xforms/Document.coffee
+++ src/resources-packaged/ops/javascript/orbeon/xforms/Document.coffee
@@ -29,7 +29,7 @@
             args = newArgs

         # Use first XForms form, if none is specified
-        args.form = _.head f$.filter '.xforms-form', $ document.forms unless args.form?
+        args.form = _.head($(document.forms).filter('.xforms-form')) unless args.form?
         event = new AjaxServer.Event args
         AjaxServer.fireEvents [event], incremental? and incremental


Revert "entryPath"

Revert "entryPath"

This reverts commit 8ec16374fb555e6fb6f4f704c47778794367e8b2.

--- config/environments/paths.coffee
+++ config/environments/paths.coffee
@@ -1,8 +1,7 @@
 # root path to rendr lib
 exports.rootDir = __dirname + '/../..'

-exports.entryPath = (process.env.PWD || '.') + '/app';  # path to parent-package/app directory
-console.log("entryPath=", exports.entryPath);
+exports.entryPath = process.env.PWD + '/app';  # path to parent-package/app directory
 exports.viewDir = exports.entryPath + '/views'
 exports.publicDir = exports.entryPath + '/../public'
 exports.staticDir = exports.entryPath + '/../static'


small simplification with $getPath

small simplification with $getPath

--- src/batman.coffee
+++ src/batman.coffee
@@ -4690,9 +4690,7 @@
       # If we're working with an `@key` and not an `@value`, find the context the key belongs to so we can
       # hold a reference to it for passing to the `dataChange` and `nodeChange` observers.
       if k = @get('key')
-        if keyContext = @get('keyContext')
-          prop = Batman.Property.forBaseAndKey(keyContext, k)
-          Batman.RenderContext.deProxy(prop.getValue())
+        Batman.RenderContext.deProxy($getPath(this, ['keyContext', k]))
       else
         @get('value')
     set: (_, value) ->


cleanup unused code

cleanup unused code

--- public/javascripts/tetris.coffee
+++ public/javascripts/tetris.coffee
@@ -161,16 +161,11 @@
     @draw()
   draw: ->
     @drawBlock(block.x, block.y, Tetris.abandonedBlockColor) for block in @blocks
-    @drawBounds()
   drawBlock: (x, y, color) ->
     @canvas.fillStyle = color
     x = @gridSize * x
     y = @gridSize * y
     @canvas.fillRect(x, y, @gridSize, @gridSize)
-  drawBounds: ->
-    # @canvas.fillStyle = "rgba(255,255,255,0.5)"
-    # @canvas.fillRect(0, 0, 320, 48)
-    # @canvas.strokeRect(0, 0, 320, 48)

 bindPusherEvents = ->
   channel.bind 'created', (data) ->


ah, fixed the spec

ah, fixed the spec

--- spec/stringTable_spec.coffee
+++ spec/stringTable_spec.coffee
@@ -86,7 +86,7 @@
         """
         | title              | opening                         |
         --------------------------------------------------------
-        | The Cat in the Hat | The did not shine.              |
+        | The Cat in the Hat | The sun did not shine.          |
         |                    | It was too wet to play.         |
         |                    | So we sat in the house          |
         |                    | All that cold, cold, wet day.   |


:fire: Delete useless line in spec

:fire: Delete useless line in spec

--- spec/text-editor-spec.coffee
+++ spec/text-editor-spec.coffee
@@ -3373,7 +3373,6 @@
     it "deletes a line only once when multiple selections are on the same line", ->
       line1 = buffer.lineForRow(1)
       count = buffer.getLineCount()
-      editor.getLastCursor().moveToTop()
       editor.setSelectedBufferRanges([
         [[0, 1], [0, 2]],
         [[0, 4], [0, 5]]


Cache js('app') and js('template/index') in dev

--- server/code/index.coffee
+++ server/code/index.coffee
@@ -230,9 +230,12 @@
   # Trust X-Forwarded-* headers
   app.enable 'trust proxy'

-
   # Add Connect Assets
+  # Grepability:
+  # js =
+  # "js" is defined in connect assets, and appears in our globals here.
   app.use assets({src: 'client'})
+
   # Set the public folder as static assets. In production this route
   # is served statically by nginx, so this has no effect. It's
   # used in dev, and not harmful in production.
@@ -243,12 +246,15 @@

 passport.use 'local', new LocalStrategy(verify)

-
 # Set View Engine
 app.set 'views', 'server/template'
 app.engine 'html', ejs.renderFile
 app.set 'view engine', 'html'
+
 js.root = 'code'
+
+js_app = js 'app'
+js_templates = js 'template/index'

 # Middleware (for checking users)
 checkThisIsMyDataHub = (req, resp, next) ->
@@ -298,8 +304,8 @@
       nav: ''
       subnav: ''
       content: ''
-      scripts: js 'app'
-      templates: js 'template/index'
+      scripts: js_app
+      templates: js_templates
       user: JSON.stringify usersObj
       recurlyDomain: process.env.RECURLY_DOMAIN
       flash: req.flash()
@@ -325,8 +331,8 @@
               nav: eco.render navTemplate.toString()
               subnav: """
#{eco.render subnavTemplate.toString(), options}
"""
               content: """
#{eco.render contentTemplate.toString(), {} }
"""
-              scripts: js 'app'
-              templates: js 'template/index'
+              scripts: js_app
+              templates: js_templates
               user: JSON.stringify usersObj
               recurlyDomain: process.env.RECURLY_DOMAIN
               flash: req.flash()


Fixed locale settings when calculating free memory

--- server/lib/memory.coffee
+++ server/lib/memory.coffee
@@ -4,7 +4,7 @@

 ControllerClient = require("cozy-clients").ControllerClient

-freeMemCmd = "free | grep /cache | cut -d':' -f2 | sed -e 's/^ *[0-9]* *//'"
+freeMemCmd = "LC_ALL=C free | grep /cache | cut -d':' -f2 | sed -e 's/^ *[0-9]* *//'"

 # Utilities to get memory consumption and disk usage.


Get current triggers for a user

Get current triggers for a user

Adding a function that takes the current triggers for a user

--- src/rivescript.coffee
+++ src/rivescript.coffee
@@ -839,6 +839,19 @@
     return undefined

   ##
+  # object getUserTopicTriggers (string username)
+  #
+  # Retrieve the triggers in current topic for the specified user. It can be used to create a UI that gives the user
+  # options based one trigges, using buttons, select and other UI resources.
+  #
+  # This will return undefined if the user cant be find
+  ##
+  getUserTopicTriggers(user) ->
+    @userVars = this.getUservars(user);
+    if(@userVars) return inherit_utils.getTopicTriggers(this, @userVars.topic);
+
+
+  ##
   # string currentUser ()
   #
   # Retrieve the current user's ID. This is most useful within a JavaScript


Removed the need to have self = @. This is way better now.

--- src/rivescript.coffee
+++ src/rivescript.coffee
@@ -393,29 +393,28 @@
   # Load a file using ajax. DO NOT CALL THIS DIRECTLY.
   _ajaxLoadFile: (loadCount, file, onSuccess, onError) ->

-    self = @
     xhr = new XMLHttpRequest();
     xhr.open "GET", file, true
-    xhr.onreadystatechange  = () ->
+    xhr.onreadystatechange  = () =>

       if xhr.readyState is 4
         if xhr.status in [0, 200]

-          self.say "Loading file #{file} complete."
+          @say "Loading file #{file} complete."

           # Parse it!
-          self.parse file, xhr.responseText, onError
+          @parse file, xhr.responseText, onError

           # Log that we've received this file.
-          delete self._pending[loadCount][file]
+          delete @_pending[loadCount][file]

           # All gone?
-          if Object.keys(self._pending[loadCount]).length is 0
+          if Object.keys(@_pending[loadCount]).length is 0
             if typeof(onSuccess) is "function"
               onSuccess.call undefined, loadCount

         else
-          self.say "Ajax error! #{xhr.statusText}; #{xhr.status}"
+          @say "Ajax error! #{xhr.statusText}; #{xhr.status}"
           if typeof(onError) is "function"
             onError.call undefined, xhr.statusText, loadCount


target charts: added some padding

target charts: added some padding

--- app/assets/javascripts/d3/target_bar.coffee
+++ app/assets/javascripts/d3/target_bar.coffee
@@ -37,7 +37,8 @@
       for query in [@success_query, @value_query, @target_query]
         max = x if (x = query.future_value()) > max
         max = x if (x = query.present_value()) > max
-      max
+      # Let's add some padding
+      max * 1.05

     min_value: => if (m = @get('min')) then m else 0


Restrict javascript events scope + Add method to remove a single layer

--- app/assets/javascripts/map_editor.js.coffee
+++ app/assets/javascripts/map_editor.js.coffee
@@ -124,9 +124,8 @@
       this._refreshControls()
       # console.log "controlled"

-      $(document).on 'click', '.updateAttributesInPopup', (e) =>
+      $(this.mapElement).on 'click', '.updateAttributesInPopup', (e) =>
         e.preventDefault()
-
         featureId = $(e.currentTarget).closest('.leaflet-popup-content').find('*[data-internal-id]').data('internal-id')
         newName = $(e.currentTarget).closest('.popup-content').find('input[type="text"]').val()

@@ -137,7 +136,7 @@
         #update popup
         this.popupize layer.feature, layer

-        $(document).trigger('mapeditor:feature_update', layer.feature)
+        $(this.element).trigger('mapeditor:feature_update', layer.feature)

         widget._saveUpdates()
         false
@@ -160,6 +159,11 @@

     navigateToLayer: (layer) ->
       this.map.fitBounds layer.getBounds()
+
+
+    removeLayer: (layer) ->
+      this.map.removeLayer layer
+

     popupize: (feature, layer) ->
       popup = ""
@@ -245,7 +249,7 @@
         if this.options.useFeatures
           this.edition = L.geoJson(this.options.edit, {
             onEachFeature: (feature, layer) =>
-              $(document).trigger('mapeditor:feature_add', feature)
+              $(this.element).trigger('mapeditor:feature_add', feature)

               if feature.properties?
                 this.popupize(feature, layer)


Fix #795: Close lightbox only if buttons, ESC key or the X icon is pressed

--- app/coffee/modules/common/lightboxes.coffee
+++ app/coffee/modules/common/lightboxes.coffee
@@ -31,18 +31,12 @@
             lightbox.addClass('open')
         ), 70

-        lightbox.on 'click', (e) =>
-            target = angular.element(e.target)
-            if target[0] == lightbox[0]
-                @close(lightbox)
-
         $(document)
             .on 'keydown.lightbox', (e) =>
                 code = if e.keyCode then e.keyCode else e.which

                 if code == 27
                     @close(lightbox)
-

     close: (lightbox) ->
         $(document).off('.lightbox')
@@ -52,6 +46,7 @@
             .removeClass('open')

 module.service("lightboxService", LightboxService)
+

 LightboxDirective = (lightboxService) ->
     link = ($scope, $el, $attrs) ->


simplify code

simplify code

--- script.coffee
+++ script.coffee
@@ -62,8 +62,7 @@
       title = $(this).text().replace(titleRe, '$1')
       categoryAndRoom = $(this).text().replace(titleRe, '$2')
       divId = "#{sourceId}_#{year}#{month}"
-      selected = false
-      urlObj = {url, year, month, divId, selected}
+      urlObj = {url, year, month, divId, selected: false}

       if sourceId of stats # CoffeeScript 'of' equals JS 'in'
         stats[sourceId].urls.push urlObj
@@ -73,20 +72,18 @@
     ractive.set stats: stats

   @go: ->
-    @loadFile()
-      .then(@parseHtml)
+    @loadFile().then(@parseHtml)

 Stat.go()

 ractive = new Ractive
   el: 'output'
   template: '#template'
-  data: stats={}

 ractive.on
   select: (event, url, title) ->
     kp = @get "#{event.keypath}"
-    @set "#{event.keypath}.selected", !kp.selected
+    @toggle "#{event.keypath}.selected"
     if kp.selected
       new Graph
         name: "#{title} #{kp.year}-#{kp.month}"


Client can now control its snake's direction

--- server.coffee
+++ server.coffee
@@ -1,4 +1,5 @@
 sys = require 'sys'
+util = require 'util'
 websocket = require 'websocket-server'
 server = websocket.createServer()

@@ -9,29 +10,41 @@

 class Snake
 	constructor: (@id) ->
+		@direction = "right"
 		@elements = [[-8, 200], [-7, 200], [-6, 200], [-5, 200], [-4, 200], [-3, 200], [-2, 200], [-1, 200]]
-		@direction = "right"
-
+
 	doStep: ->
-		moveElement i for i in [0..7]
-
-	moveElement: (i) ->
+		@moveTail i for i in [0..6]
+		@moveHead 7
+
+	moveTail: (i) ->
+		@elements[i][0] = @elements[i+1][0]
+		@elements[i][1] = @elements[i+1][1]
+
+	moveHead: (i) ->
 		switch @direction
 			when "left" then @elements[i][0] -= 1
-			when "right" then @elements[i][1] += 1
-			when "up" then @elements[i][0] -= 1
-			when "down" then @elements[i][0] += 1
+			when "right" then @elements[i][0] += 1
+			when "up" then @elements[i][1] -= 1
+			when "down" then @elements[i][1] += 1
+		sys.puts(util.inspect @elements, @direction)
+

 ### Handle Connections ###

 server.addListener "connection", (connection) ->
 	clientId = autoClient
+	clientSnake = new Snake clientId
+
 	autoClient += 1
-
-	snakes.push new Snake clientId
+	snakes.push clientSnake
+
+	sys.puts "Client #{clientId} connected"

 	connection.addListener "message", (message) ->
-		sys.puts("Client #{clientId} says: " + message)
+		message = JSON.parse(message)
+		sys.puts("Client #{clientId}: " + message.direction)
+		clientSnake.direction = message.direction

 server.addListener "close", (connection) ->
 	sys.puts("Client disconnected")
@@ -39,9 +52,11 @@
 ### Update Game State ###

 updateState = ->
-	server.broadcast JSON.stringify({'server': 'test'})
+	sys.puts "Doing step for #{snakes.length} snakes"
+	snake.doStep() for snake in snakes
+	# server.broadcast JSON.stringify({'server': 'test'})

-# tick = setInterval updateState, 5000
+tick = setInterval updateState, 5000

 ### Start Server ###


[minor] added the tag route

[minor] added the tag route

--- server.coffee
+++ server.coffee
@@ -1,3 +1,4 @@
+async = require 'async'
 fs = require 'fs'
 journey = require 'journey'
 http = require 'http'
@@ -19,7 +20,35 @@
                 return res.send 404, {}, err
             return res.send 200, {}, data

-    this.get(/([a-z\-\.]+)/).bind (req, res, name)->
+    this.get("/tag").bind (req, res)->
+         fs.readdir "topics/", (err, results)->
+            if err
+                return res.send 404, {}, err
+            tags = {}
+
+            iter = (dir, callback)->
+                fs.readFile "topics/"+dir+'/metadata.json', 'utf8', (err,json)->
+                    if err
+                        return callback err
+                    obj = JSON.parse json
+                    for tag in obj.tags
+                        tags[tag] = ""
+                    callback()
+
+            async.forEach results, iter, (err)->
+                if err
+                    return res.send 404, {}, err
+
+                tags_array = []
+
+                for tag of tags
+                    tags_array.push tag
+
+                return res.send 200, {}, JSON.stringify tags_array
+
+
+
+    this.get(/article\/([a-z\-\.]+)/).bind (req, res, name)->
         fs.readFile "topics/"+name+'/article', 'utf8', (err,article)->
           fs.readFile "topics/"+name+'/metadata.json', 'utf8', (err,json)->
             json ?= "{}"


removed some logging

removed some logging

--- server.coffee
+++ server.coffee
@@ -130,9 +130,7 @@
   if (data.y <= 1)  #end of game
     pipe.channel(gameID).trigger('endOfGame')
     delete games[gameID]
-    console.log gameIDs
     gameIDs.splice(gameIDs.indexOf(gameID), 1);
-    console.log gameIDs

 pipe.channels.on 'event:ready', (gameID, socketID, data) ->
   game = games[gameID]


Fix for onGeoloc notifications

Fix for onGeoloc notifications

--- server.coffee
+++ server.coffee
@@ -57,13 +57,11 @@
 	log '[onGeoloc()] Geoloc from ' + Plugin.userName(userId) + '('+userId+'): ', JSON.stringify(geoloc)
 	recieved = new Date()/1000
 	if Db.shared.peek('gameState') is 1 and (recieved - (Db.shared.peek('lastNotification', userId) || 0))> 30*60
-		beacons = Db.shared.ref('game', 'beacons')
 		beaconRadius = Db.shared.peek('game', 'beaconRadius')
 		found=false;
 		#Check if user is in range of an enemy beacon, opening the app will capture the beacon
-		beacons.iterate (beacon) ->
-			if beacon.peek('owner') isnt getTeamOfUser(userId) and !found
-				#log distance(geoloc.latitude, geoloc.longitude, beacon.peek('location', 'lat'), beacon.peek('location', 'lng'))
+		Db.shared.iterate 'game', 'beacons', (beacon)!->
+			if (parseInt(beacon.peek('owner'),10) != parseInt(getTeamOfUser(userId),10)) and !found
 				if distance(geoloc.latitude, geoloc.longitude, beacon.peek('location', 'lat'), beacon.peek('location', 'lng')) < beaconRadius
 					#send notifcation
 					Event.create
@@ -481,7 +479,7 @@

 # Get the team id the user is added to
 getTeamOfUser = (userId) ->
-	result = undefined
+	result = undefined;
 	Db.shared.iterate 'game', 'teams', (team) !->
 		if team.peek('users', userId, 'userName')?
 			result = team.key()


use custom renderer for carated headings

--- server.coffee
+++ server.coffee
@@ -8,14 +8,19 @@
 hmz = require 'humanize-plus'
 mkd = require 'marked'
 pth = require 'path'
+rdr = new mkd.Renderer
 tof = require 'toffee'

 cfg = {
 	port: 4747
 }

+rdr.heading = (text,level) ->
+	return "<h#{level}>> #{text}</h#{level}>"
+
 mkd.setOptions {
 	highlight: (code) -> hlt.highlightAuto(code).value
+	renderer: rdr
 }


Fix OPTIONS accumulation on OPTIONS requests

--- api/tools/optionsroute.coffee
+++ api/tools/optionsroute.coffee
@@ -1,12 +1,11 @@
 _ = require 'lodash'

 module.exports = (options) ->
+    if not _.isArray options
+        options = [options]
+    options.push 'OPTIONS'
+
     (req, res, next) ->
-        if not _.isArray options
-            options = [options]
-
-        options.push 'OPTIONS'
-
         res.header 'Access-Control-Allow-Methods', options.join ", "
         res.header 'Access-Control-Allow-Headers', 'Content-Type'
         res.header 'Access-Control-Max-Age', 0


Edit bushbuck text

Edit bushbuck text

Removed parentheses from scientific name.

--- app/lib/field-guide-content/bushbuck.coffee
+++ app/lib/field-guide-content/bushbuck.coffee
@@ -3,7 +3,7 @@
     Bushbucks are solitary antelopes that are yellowish or reddish-tan in color with a white underside and thin, white vertical stripes running from shoulder to rump. They have bushy tails and geometrically shaped white patches on their ears, chin, tail, legs, and necks. Males have horns that can grow up to 57 cm (or 23 inches) long.
   '''

-  scientificName: '(Tragelaphus scriptus)'
+  scientificName: 'Tragelaphus scriptus'
   mainImage: 'assets/fieldguide-content/mammals/bushbuck/bushbuck-feature.jpg'

   information: [{


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera.coffee
+++ src/camera.coffee
@@ -41,7 +41,7 @@
       I.scroll.x = I.scroll.x.approach(centerRect.left, I.maxVelocity.x)

     if objectCenter.y > centerRect.bottom
-      I.scroll.y = I.scroll.x.approach(centerRect.right, I.maxVelocity.x)
+      I.scroll.y = I.scroll.y.approach(centerRect.right, I.maxVelocity.x)
     else if objectCenter.x < centerRect.left
       I.scroll.x = I.scroll.x.approach(centerRect.left, I.maxVelocity.x)


client: debug to get initi error.

client: debug to get initi error.

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

         validate: (attrs, options) ->
             for key of @defaults
-                if not attrs[key]
-                    return "#{ key } is required."
+                if not attrs[key]?
+                    return "The model must have `#{ key }`."

     # Hipster views have mustache templates.
     class HipsterView extends Backbone.View
@@ -106,7 +106,8 @@
     # Entry point -- call `main` with initial model, config and parameter
     # `data` to setup the client application.
     main = (data) ->
-        model = new Backbone.Model
+        model = new Model
+        model.on 'invalid', (m, error) -> throw error
         generator = new CodeGenerator model
         controls = new ControlsView el: 'controls', model: model
         model.set data, validate: true


Fix typo

Fix typo

--- src/client.coffee
+++ src/client.coffee
@@ -289,7 +289,7 @@
             return false
         else
             message.id = ++@_messageID
-            message.seq = id
+            message.seq = message.id
             @_pending[message.id] = message
             @ws.send JSON.stringify(message)
             return message


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color2.coffee
+++ src/color2.coffee
@@ -16,7 +16,9 @@
     switch hexString.length
       when 3, 4
         if hexString.length == 4
-          alpha = ((parseInt(hexString.substr(3, 1), 16) * 0x11) / 255.0) || 1.0
+          alpha = ((parseInt(hexString.substr(3, 1), 16) * 0x11) / 255.0)
+        else
+          alpha = 1.0

         rgb = (parseInt(hexString.substr(i, 1), 16) * 0x11 for i in [0..2])


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color2.coffee
+++ src/color2.coffee
@@ -136,7 +136,15 @@
       hsl = @toHsl()
       hsl[1] -= amount

-      return Color2(hslToRgb(hsl))
+      return Color2(hslToRgb(hsl))
+
+    desaturate$: (amount) ->
+      hsl = @toHsl()
+      hsl[1] -= amount
+
+      [@r, @g, @b, @a] = hslToRgb(hsl)
+
+      return this

     equal: (other) ->
       other.r == @r &&


Fix tests for #1713...

Fix tests for #1713...

... which is weird, because the tests are passing here.  Let's see what
Travis makes of this.

--- content_scripts/mode.coffee
+++ content_scripts/mode.coffee
@@ -50,11 +50,11 @@
     # If options.suppressAllKeyboardEvents is truthy, then all keyboard events are suppressed.  This avoids
     # the need for modes which block all keyboard events to 1) provide handlers for all keyboard events,
     # and 2) worry about their return value.
-    if options.suppressAllKeyboardEvents
+    if @options.suppressAllKeyboardEvents
       for type in [ "keydown", "keypress", "keyup" ]
-        do (type) ->
-          handler = options[type]
-          options[type] = (event) -> handler? event; false
+        do (type) =>
+          handler = @options[type]
+          @options[type] = (event) -> handler? event; false

     @push
       keydown: @options.keydown || null


fix direct access to one page on the dataTable

--- BackofficeBundle/Resources/public/coffee/table/TableviewCollectionView.coffee
+++ BackofficeBundle/Resources/public/coffee/table/TableviewCollectionView.coffee
@@ -49,11 +49,19 @@
     );
     $.fn.dataTable.pipeline = @dataTablePipeline

+    displayStart = 0
+    pageLength = 10
+    if @options.page?
+      page = parseInt(@options.page) - 1
+      displayStart = pageLength * page
+
     @options.table = $('#tableviewCollectionTable').dataTable(
       searching: true
       ordering: true
       processing: true
       serverSide: true
+      displayStart: displayStart
+      pageLength: pageLength
       bAutoWidth: false
       ajax : $.fn.dataTable.pipeline(
         url : @options.url
@@ -61,9 +69,6 @@
       )
       initComplete: (settings, json) ->
         viewContext.renderAddButton(viewContext, json.links, this)
-        page = parseInt(viewContext.options.page) - 1
-        if page? and page <= this.api().page.info().pages
-          this.api().page(page).draw(false)
       columns: columns
       columnDefs: columnDefs.concat [
         targets: -1


Updated data structure for grocery list

Updated data structure for grocery list

--- src/scripts/grocery-list.coffee
+++ src/scripts/grocery-list.coffee
@@ -9,19 +9,23 @@

 module.exports = (robot) ->

-  robot.brain.data.groceryList = []
+  robot.brain.data.groceryList =
+    toBuy: {},
+    purchased: {}
   groceryList =
     get: ->
-      robot.brain.data.groceryList
+      Object.keys(robot.brain.data.groceryList.toBuy)

     add: (item) ->
-      robot.brain.data.groceryList.push item
+      robot.brain.data.groceryList.toBuy[item] = true

     remove: (item) ->
-      delete robot.brain.data.groceryList[item]
+      delete robot.brain.data.groceryList.toBuy[item]
+      robot.brain.data.groceryList.purchased[item] = true

     bought: (item) ->
-      delete robot.brain.data.groceryList[item]
+      delete robot.brain.data.groceryList.toBuy[item]
+      robot.brain.data.groceryList.purchased[item] = true

   robot.respond /grocery list$/i, (msg) ->
     list = groceryList.get().join("\n") || "No items in your grocery list."


Update wunderground.coffee

Update wunderground.coffee

Improvement based on comment in pull request https://github.com/github/hubot-scripts/pull/1294 by https://github.com/technicalpickles

--- src/scripts/wunderground.coffee
+++ src/scripts/wunderground.coffee
@@ -88,10 +88,8 @@

 send_forecast = (msg, location, data) ->
   report = data.forecast.txt_forecast.forecastday[0]
-  if not process.env.HUBOT_WUNDERGROUND_USE_METRIC?
-    msg.send "#{report.title} in #{location}: #{report.fcttext} (#{formatted_ttl data})"
-  else
-    msg.send "#{report.title} in #{location}: #{report.fcttext_metric} (#{formatted_ttl data})"
+  useMetric = process.env.HUBOT_WUNDERGROUND_USE_METRIC?
+  msg.send "#{report.title} in #{location}: #{if useMetric then report.fcttext_metric else report.fcttext} (#{formatted_ttl data})"
 send_radar = (msg, location, data) ->
   msg.send "#{data.radar.image_url}#.png"


static React navigation control added

static React navigation control added

--- app/coffee/game_common.coffee
+++ app/coffee/game_common.coffee
@@ -109,11 +109,9 @@
   sgfObject or= smartgame.parse(getInputSgf() or '(;SZ[19])')
   size = parseInt(sgfObject.gameTrees[0].nodes[0].SZ, 10) or 19

-  $navBlock = createNavigationDom sgfObject
   $('#board').empty()
   React.render (React.createElement BoardAreaDom, {sgfObject: sgfObject}),
                $('#board')[0]
-  $('#board').append($elem) for $elem in [$navBlock]
   return

 BoardAreaDom = React.createClass
@@ -127,6 +125,8 @@
                               boardState: boardState
                               lastPlayed: lastPlayed
                               size: size),
+         (React.createElement NavigationDom,
+                              sgfObject: @props.sgfObject),
          (React.createElement ScoreDom, prisoners: prisoners)]

 BoardDom = React.createClass
@@ -189,7 +189,7 @@
 _moveNoListeners = []

 game_common.onViewMoveNo = (callback) ->
-  _moveNoListeners.push callback
+  # _moveNoListeners.push callback
   callback

 game_common.offViewMoveNo = ->
@@ -205,6 +205,37 @@
 game_common.isViewingLatestMove = -> _isViewingLatestMove
 game_common.viewingMoveNo = -> _viewingMoveNo

+NavigationDom = React.createClass
+  render: ->
+    {sgfObject} = @props
+
+    options = [n: 0, text: 'Start']
+    maxMoves = null
+    do ->
+      moveNo = 0
+      for node in sgfObject.gameTrees[0].nodes
+        if node.B?
+          moveNo += 1
+          options.push n: moveNo, text: 'B ' + (a1FromSgfTag(node.B) or 'pass')
+        else if node.W?
+          moveNo += 1
+          options.push n: moveNo, text: 'W ' + (a1FromSgfTag(node.W) or 'pass')
+        else if node.TB? or node.TW?
+          moveNo += 1
+          options.push n: moveNo, text: 'Mark dead'
+      maxMoves = moveNo
+
+    reactOption = (option) ->
+      {n, text} = option
+      React.DOM.option {value: n, selected: n == maxMoves},
+                       ["Move #{n}: #{text}"]
+
+    {div, select} = React.DOM
+    div {className: 'board_nav_block'},
+        (select {className: 'move_select'},
+                (reactOption o for o in options))
+
+###
 createNavigationDom = (sgfObject) ->
   maxMoves =  null

@@ -252,6 +283,7 @@

   $navBlock.append $select
   $navBlock
+###

 a1FromSgfTag = (tag) ->
   if typeof tag is 'string'