Corrige deploy

Corrige deploy

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -3,16 +3,21 @@

 module.exports = (grunt) ->
 	pacha = grunt.file.readJSON('tools/pachamama/pachamama.config')[0]
+	whoami = grunt.file.readJSON('meta/whoami')
+	# Tags
+	tagApplicationRoot = "<%=@" + pacha.acronym + "_root%>"
+
 	# Project configuration.
 	grunt.initConfig
-		resourceToken: process.env['RESOURCE_TOKEN'] or '/io'
+		# App variables
+		relativePath: ''
+		applicationRoot: process.env['APPLICATION_ROOT'] or whoami.roots[0]
+		deployDirectory: path.normalize(process.env['DEPLOY_DIRECTORY'] ? 'deploy')
 		gitCommit: process.env['GIT_COMMIT'] or 'GIT_COMMIT'
-		deployDirectory: path.normalize(process.env['DEPLOY_DIRECTORY'] ? 'deploy')
-		relativePath: ''
-		pkg: grunt.file.readJSON('package.json')
-		pacha: pacha
+
+		# Version variables
 		acronym: pacha.acronym
-		environmentName: process.env['ENVIRONMENT_NAME'] or '1-0-0'
+		environmentName: process.env['ENVIRONMENT_NAME'] or '01-00-00'
 		buildNumber: process.env['BUILD_NUMBER'] or '1'
 		environmentType: process.env['ENVIRONMENT_TYPE'] or 'stable'
 		versionName: -> [grunt.config('acronym'), grunt.config('environmentName'), grunt.config('buildNumber'),
@@ -22,30 +27,30 @@
 			main:
 				expand: true
 				cwd: 'src/'
-				src: ['**', '!includes/**', '!coffee/**', '!**/*.less']
+				src: ['**', '!coffee/**', '!**/*.less']
 				dest: 'build/<%= relativePath %>'
+
+			debug:
+				src: ['src/index.html']
+				dest: 'build/<%= relativePath %>/index.debug.html'
+
+			commit:
+				expand: true
+				cwd: 'build/<%= relativePath %>/'
+				src: ['**', '!coffee/**', '!**/*.less']
+				dest: '<%= deployDirectory %>/<%= gitCommit %>/'
+
+			version:
+				expand: true
+				cwd: '<%= deployDirectory %>/<%= gitCommit %>/'
+				src: ['**']
+				dest: '<%= deployDirectory %>/<%= versionName() %>/'

 			test:
 				expand: true
 				cwd: 'spec/'
 				src: ['**', '!**/*.coffee']
 				dest: 'build/<%= relativePath %>/spec/'
-
-			debug:
-				src: ['src/index.html']
-				dest: 'build/<%= relativePath %>/index.debug.html'
-
-			deploy:
-				expand: true
-				cwd: 'build/<%= relativePath %>/'
-				src: ['**', '!includes/**', '!coffee/**', '!**/*.less']
-				dest: '<%= deployDirectory %>/<%= gitCommit %>/'
-
-			env:
-				expand: true
-				cwd: '<%= deployDirectory %>/<%= gitCommit %>/'
-				src: ['**']
-				dest: '<%= deployDirectory %>/<%= versionName() %>/'

 			dist:
 				expand: true
@@ -98,21 +103,34 @@
 				singleRun: true

 		'string-replace':
-			deploy:
-				files:
-					'<%= deployDirectory %>/<%= versionName() %>/index.html': ['<%= deployDirectory %>/<%= versionName() %>/index.html']
-					'<%= deployDirectory %>/<%= versionName() %>/index.debug.html': ['<%= deployDirectory %>/<%= versionName() %>/index.debug.html']
+			# This replacement treats src and href attributes which cannot contain variables in order for
+			# the minification process to work. It adds the applicationRoot tag variables before the files address.
+			commit:
+				files:
+					'<%= deployDirectory %>/<%= gitCommit %>/index.html': ['<%= deployDirectory %>/<%= gitCommit %>/index.html']
+					'<%= deployDirectory %>/<%= gitCommit %>/index.debug.html': ['<%= deployDirectory %>/<%= gitCommit %>/index.debug.html']

 				options:
 					replacements: [
-						pattern: /src="(\.\.\/)?(?!http|\/|\/\/|\#)/ig
-						replacement: 'src="/io/messages/'
+						pattern: /src="(\.\.\/)?(?!http|\/|\/\/|\#|\&|\'\&)/ig
+						replacement: 'src="' + tagApplicationRoot
 					,
-						pattern: /href="(\.\.\/)?(?!http|\/|\/\/|\#)/ig
-						replacement: 'href="/io/messages/'
+						pattern: /href="(\.\.\/)?(?!http|\/|\/\/|\#|\&|\'\&)/ig
+						replacement: 'href="' + tagApplicationRoot
 					,
 						pattern: '<script src="http://localhost:35729/livereload.js"></script>'
 						replacement: ''
+					]
+			# Here, all tag variables are replaced for production-ready endpoints.
+			version:
+				files:
+					'<%= deployDirectory %>/<%= versionName() %>/index.html': ['<%= deployDirectory %>/<%= versionName() %>/index.html']
+					'<%= deployDirectory %>/<%= versionName() %>/index.debug.html': ['<%= deployDirectory %>/<%= versionName() %>/index.debug.html']
+
+				options:
+					replacements: [
+						pattern: new RegExp(tagApplicationRoot, "gi")
+						replacement: '<%= applicationRoot %>/'
 					]

 		connect:
@@ -170,23 +188,26 @@
 	# Dist
 	grunt.registerTask 'dist', ['prod', 'copy:dist']

+	# Tasks for deploy build
+	grunt.registerTask 'gen-commit', ['clean', 'copy:main', 'copy:test', 'coffee', 'less', 'copy:debug',
+										'useminPrepare', 'concat', 'uglify', 'cssmin', 'usemin',
+										'copy:commit', 'string-replace:commit']
+
 	# Generates version folder
 	grunt.registerTask 'gen-version', ->
-		grunt.log.writeln 'Deploying to environmentName: '.cyan + grunt.config('environmentName').green
-		grunt.log.writeln 'Deploying to buildNumber: '.cyan + grunt.config('buildNumber').green
-		grunt.log.writeln 'Deploying to environmentType: '.cyan + grunt.config('environmentType').green
-		grunt.log.writeln 'Directory: '.cyan + grunt.config('versionName')().green
-		grunt.log.writeln 'Version set to: '.cyan + grunt.config('gitCommit').green
-		grunt.log.writeln 'Rersource token set to: '.cyan + grunt.config('resourceToken').green
-		grunt.log.writeln 'Deploy folder: '.cyan + grunt.config('deployDirectory').green
-		grunt.task.run ['copy:env', 'string-replace:deploy']
+		grunt.log.writeln 'applicationRoot: '.cyan + grunt.config('applicationRoot').green
+		grunt.log.writeln 'environmentName: '.cyan + grunt.config('environmentName').green
+		grunt.log.writeln 'buildNumber: '.cyan + grunt.config('buildNumber').green
+		grunt.log.writeln 'environmentType: '.cyan + grunt.config('environmentType').green
+		grunt.log.writeln 'Version deploy directory: '.cyan + (path.resolve grunt.config('deployDirectory'), grunt.config('versionName')()).green
+		grunt.task.run ['copy:version', 'string-replace:version']

 	# Deploy - creates deploy folder structure
 	grunt.registerTask 'deploy', ->
 		commit = grunt.config('gitCommit')
 		deployDir = path.resolve grunt.config('deployDirectory'), commit
 		deployExists = false
-		grunt.log.writeln 'Version deploy dir set to: '.cyan + deployDir.green
+		grunt.log.writeln 'Commit deploy directory: '.cyan + deployDir.green
 		try
 			deployExists = fs.existsSync deployDir
 		catch e
@@ -195,10 +216,10 @@

 		if deployExists
 			grunt.log.writeln 'Folder '.cyan + deployDir.green + ' already exists.'.cyan
-			grunt.log.writeln 'Skipping build process and generating environmentType folder.'.cyan
-			grunt.task.run ['clean', 'gen-version']
+			grunt.log.writeln 'Skipping build process and generating version folder.'.cyan
+			grunt.task.run ['gen-version']
 		else
-			grunt.task.run ['prod', 'karma:deploy', 'copy:deploy', 'gen-version']
+			grunt.task.run ['gen-commit', 'karma:deploy', 'gen-version']

 	#	Remote task
 	grunt.registerTask 'remote', 'Run Remote proxy server', ->


Fix bug

Fix bug

Warning: Unable to read "dist/" file (Error code: EISDIR).

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -182,7 +182,7 @@
         options:
           join: true
         files:
-          'dist/leaf.js': files.all 'observable', 'src'
+          'dist/leaf.js': files.all 'view', 'src'

     # Concat
     concat:
@@ -190,9 +190,7 @@
         options:
           banner: '<%= meta.banner %>'
         files:
-          cwd: DIST_DIR
-          dest: DIST_DIR
-          src: ['leaf.js']
+          'dist/leaf.js': ['dist/leaf.js']

     # Uglify
     uglify:
@@ -201,10 +199,7 @@
           banner: '<%= meta.banner %>'
           report: 'gzip'
         files:
-          cwd: DIST_DIR
-          dest: DIST_DIR
-          src: ['leaf.js']
-          ext: '.min.js'
+          'dist/leaf.min.js': ['dist/leaf.js']

     # Jasmine
     jasmine:


Add missing CoffeeScript subtasks.

Add missing CoffeeScript subtasks.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -73,6 +73,6 @@
   @loadNpmTasks 'grunt-mocha-phantomjs'
   @loadNpmTasks 'grunt-contrib-watch'

-  @registerTask 'build', ['peg', 'componentbuild', 'uglify', 'coffee']
-  @registerTask 'test', ['build', 'cafemocha', 'mocha_phantomjs']
+  @registerTask 'build', ['coffee:src', 'peg', 'componentbuild', 'uglify']
+  @registerTask 'test', ['build', 'coffee:spec', 'cafemocha', 'mocha_phantomjs']
   @registerTask 'default', ['build']


Update grunt task for releasing downloads

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -102,12 +102,42 @@
         files: [
           { expand: true, cwd: 'build/', src: 'chaplin.{js,min.js}' },
           {
+            dest: 'bower.json',
+            body: {
+              name: 'chaplin',
+              repo: 'chaplinjs/downloads',
+              version: pkg.version,
+              main: 'chaplin.js',
+              scripts: ['chaplin.js'],
+              dependencies: { backbone: '1.x' }
+            }
+          },
+          {
             dest: 'component.json',
             body: {
               name: 'chaplin',
+              repo: 'chaplinjs/downloads',
               version: pkg.version,
               main: 'chaplin.js',
-              dependencies: { backbone: '>= 1.0.0' }
+              scripts: ['chaplin.js'],
+              dependencies: { 'components/backbone': '1.0.0' }
+            }
+          },
+          {
+            dest: 'package.json',
+            body: {
+              name: 'chaplin',
+              version: pkg.version,
+              description: 'Chaplin.js',
+              main: 'chaplin.js',
+              scripts: { test: 'echo "Error: no test specified" && exit 1' },
+              repository: {
+                type: 'git', url: 'git://github.com/chaplinjs/downloads.git'
+              },
+              author: 'Chaplin team',
+              license: 'MIT',
+              bugs: { url: 'https://github.com/chaplinjs/downloads/issues' },
+              dependencies: { backbone: '~1.0.0', underscore: '~1.5.1' }
             }
           }
         ]


Remove grunt file

Remove grunt file

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,8 +0,0 @@
-module.exports = (grunt) ->
-
-  grunt.loadNpmTasks('grunt-karma');
-
-  grunt.initConfig
-    karma:
-      unit:
-        configFile: 'karmaConfig.coffee'


Watch library files and templates as well

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -88,7 +88,10 @@
         tasks: ["browserify"]
       views:
         files: ["views/**/*.jade"]
-        tasks: []
+        tasks: ["browserify"]
+      libs:
+        files: ["assets/javascripts/libs/*.js"]
+        tasks: ["copy"]
       self:
         files: "Gruntfile.coffee"
         tasks: ["default"]


Fixup 5036782, update Gruntfile to allow running helpers spec.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -66,7 +66,7 @@
         options:
           specs: [
             'tests/js/*.js'
-            '!tests/js/helpers.js'
+            'tests/js/fakes.js'
           ]
           host: 'http://127.0.0.1:8000/'
           template: require 'grunt-template-jasmine-requirejs'


Removed a garbage code in `pluckDirectCall`.

`args` are removed at 094b876a384bed5504f5.

--- nodes.coffee
+++ nodes.coffee
@@ -1585,7 +1585,6 @@
       base  = new Value ref
       if val.base
         [val.base, base] = [base, val]
-        args.unshift new Literal 'this'
       body.expressions[idx] = new Call base, expr.args
       defs += @tab + new Assign(ref, fn).compile(o, LEVEL_TOP) + ';\n'
     defs


Server: Tests: remove unused daisy

Server: Tests: remove unused daisy

--- servers/lib/server/handlers/api/logs.test.coffee
+++ servers/lib/server/handlers/api/logs.test.coffee
@@ -1,5 +1,4 @@
-{ daisy
-  expect
+{ expect
   request
   generateUrl
   deepObjectExtend


Removed FactlinkConfig

Removed FactlinkConfig

--- app/js/publisher_poc/nu.nl.coffee
+++ app/js/publisher_poc/nu.nl.coffee
@@ -6,23 +6,9 @@
   */
 (function(e,t,n){typeof module!="undefined"&&module.exports?module.exports=n():typeof define=="function"&&define.amd?define(n):t[e]=n()})("$script",this,function(){function v(e,t){for(var n=0,r=e.length;n<r;++n)if(!t(e[n]))return f;return 1}function m(e,t){v(e,function(e){return!t(e)})}function g(e,t,a){function d(e){return e.call?e():r[e]}function b(){if(!--p){r[h]=1,c&&c();for(var e in s)v(e.split("|"),d)&&!m(s[e],d)&&(s[e]=[])}}e=e[l]?e:[e];var f=t&&t.call,c=f?t:a,h=f?e.join(""):t,p=e.length;return setTimeout(function(){m(e,function(e){if(e===null)return b();if(u[e])return h&&(i[h]=1),u[e]==2&&b();u[e]=1,h&&(i[h]=1),y(!n.test(e)&&o?o+e+".js":e,b)})},0),g}function y(n,r){var i=e.createElement("script"),s=f;i.onload=i.onerror=i[d]=function(){if(i[h]&&!/^c|loade/.test(i[h])||s)return;i.onload=i[d]=null,s=1,u[n]=2,r()},i.async=1,i.src=n,t.insertBefore(i,t.firstChild)}var e=document,t=e.getElementsByTagName("head")[0],n=/^https?:\/\//,r={},i={},s={},o,u={},a="string",f=!1,l="push",c="DOMContentLoaded",h="readyState",p="addEventListener",d="onreadystatechange";return!e[h]&&e[p]&&(e[p](c,function b(){e.removeEventListener(c,b,f),e[h]="complete"},f),e[h]="loading"),g.get=y,g.order=function(e,t,n){(function r(i){i=e.shift(),e.length?g(i,r):g(i,t,n)})()},g.path=function(e){o=e},g.ready=function(e,t,n){e=e[l]?e:[e];var i=[];return!m(e,function(e){r[e]||i[l](e)})&&v(e,function(e){return r[e]})?t():!function(e){s[e]=s[e]||[],s[e][l](t),n&&n(i)}(e.join("|")),g},g.done=function(e){g([null],e)},g})
 `
-localConfig =
-  api: "http://localhost:3000"
-  lib: "http://localhost:8000/lib/dist"
-  srcPath: "/factlink.core.js"
-  url: window.location.href
-
-remoteConfig =
-  api: "https://factlink-testserver.inverselink.com:443"
-  lib: "https://factlink-static-testserver.inverselink.com:443/lib/dist"
-  srcPath: "/factlink.core.min.js"
-  url: window.location.href
-
-window.FactlinkConfig = localConfig
-
 $script(['//code.jquery.com/jquery-1.10.2.min.js',
          '//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.5.2/underscore-min.js',
-         "#{window.FactlinkConfig.lib}/factlink_loader_basic.js"
+         "#{window.FactlinkConfig.lib}/factlink_loader_basic.#{window.FactlinkConfig.minified}js"
          ], 'myBundle')

 $script.ready 'myBundle', ->


ResourceListItem: pass stack data as option that we need for future requirements

--- client/admin/lib/views/resources/resourcelistitem.coffee
+++ client/admin/lib/views/resources/resourcelistitem.coffee
@@ -32,6 +32,8 @@

     listView          = new MachinesList
       itemClass       : ResourceMachineItem
+      itemOptions     : { stack: resource }
+
     controller        = new MachinesListController
       view            : listView
       wrapper         : no


drop some little black magic in here

drop some little black magic in here

--- library/exposure/localized.coffee
+++ library/exposure/localized.coffee
@@ -70,7 +70,7 @@
     # This allows you to override type definitions that may be used in
     # a parent classes, without having to replace implementation code.
     # Allows to inject arbitrary lexical-local values to external fns.
-    @considering i18n: external inline: yes, -> @t
+    @considering i18n: external inline: yes, -> this.t

     # An implementation of the standard, system hook in order for
     # providing a static boilerplate that is used to bring up the


minor changes to the bilateral

minor changes to the bilateral

--- library/membrane/bilateral.coffee
+++ library/membrane/bilateral.coffee
@@ -134,8 +134,8 @@
             return overwrap.call(s...)(c...) if @__isolated
             name = _.findKey p, (x) -> return x is overwrap
             assert socket = container?.socket or container
-            assert socket._events?, "no container/socket"
-            guarded = type.guarded implement, socket, e
+            assert socket._events?, "no container or socket"
+            guarded = type.guarded externalized, socket, e
             assert run = guarded.run.bind(guarded) or no
             @createLinkage socket, name, directives, run
         remoted = _.isObject(implement.remote or null)


we should not use `console.log` in production code...

--- app/assets/javascripts/dispatcher.js.coffee
+++ app/assets/javascripts/dispatcher.js.coffee
@@ -1,6 +1,6 @@
 $ ->
   new Dispatcher()
-
+
 class Dispatcher
   constructor: () ->
     @initSearch()
@@ -10,8 +10,6 @@
     page = $('body').attr('data-page')
     project_id = $('body').attr('data-project-id')

-    console.log(page)
-
     unless page
       return false


Handle geojson features on draw and on reference layer + method to force update

--- app/assets/javascripts/map_editor.js.coffee
+++ app/assets/javascripts/map_editor.js.coffee
@@ -98,8 +98,15 @@

       widget = this

-      this.map.on "draw:created", (e) ->
-        widget.edition.addLayer e.layer
+      this.map.on "draw:created", (e) =>
+        #Attempt to add a geojson feature
+        try
+          feature = e.layer.toGeoJSON()
+          feature.properties['internal_id'] = new Date().getTime()
+          widget.edition.addData feature
+        catch
+          widget.edition.addLayer e.layer
+
         widget._saveUpdates()
         widget.element.trigger "mapchange"

@@ -139,6 +146,7 @@
         $(this.element).trigger('mapeditor:feature_update', layer.feature)

         widget._saveUpdates()
+        $(this.element).trigger('mapchange')
         false

       widget.element.trigger "mapeditor:loaded"
@@ -152,7 +160,7 @@
     findLayer: (feature_id) ->
       containerLayer = undefined
       this.edition.eachLayer (layer) =>
-        if (layer.feature.properties.internal_id == feature_id)
+        if (parseInt(layer.feature.properties.internal_id) == feature_id)
           containerLayer = layer
           return
       return containerLayer
@@ -162,7 +170,7 @@

     removeLayer: (layer) ->
-      this.map.removeLayer layer
+      this.edition.removeLayer layer

     popupize: (feature, layer) ->
@@ -238,7 +246,13 @@
       if this.reference?
         this.map.removeLayer this.reference
       if this.options.show?
-        this.reference = L.GeoJSON.geometryToLayer(this.options.show).setStyle this.options.showStyle
+        if this.options.useFeatures
+          this.reference = L.geoJson(this.options.show)
+        else
+          this.reference = L.GeoJSON.geometryToLayer(this.options.show)
+
+        this.reference.setStyle this.options.showStyle
+#        this.reference = L.GeoJSON.geometryToLayer(this.options.show).setStyle this.options.showStyle
         this.reference.addTo this.map
       this

@@ -464,6 +478,10 @@
         this.element.val JSON.stringify(this.edition.toGeoJSON())
       true

+    update: ->
+      this._saveUpdates()
+      this.element.trigger "mapchange"
+
   $(document).ready ->
     $("input[data-map-editor]").each ->
       $(this).mapeditor()


reenable submit button if pressed too early

--- app/assets/javascripts/pv_queries.js.coffee
+++ app/assets/javascripts/pv_queries.js.coffee
@@ -3,7 +3,8 @@
 # You can use CoffeeScript in this file: http:#coffeescript.org/
 ready = ->
   $('#enable_js').hide()
-  form_validation = $('#new_pv_query').validate# <<<
+  form = $('#new_pv_query')
+  form_validation = form.validate# <<<
     #debug: true,
     errorClass: 'alert-warning',
     errorPlacement: (error, element) ->
@@ -108,7 +109,7 @@
       new_compass_angle = 105
     else if 180 < bearing <= 225
       new_compass_angle = 10
-    else if -134 < bearing <= -90 #bearing calculation switches to negative values here
+    else if -134 < bearing <= -90 #bearing calculation switches to )egative values here
       new_compass_angle = 10
     else if -90 < bearing <= 0
       new_compass_angle = -80
@@ -123,7 +124,13 @@
 # >>>
   #prevent double submit
   $('#new_pv_query').submit ->
+    $('input[type=submit]', this).addClass('.test')
+
     $('input[type=submit]', this).attr('disabled', 'disabled')
+  #reenable submit button
+  $('input.form-control').change ->
+    if $('input[type=submit]').attr('disabled') == 'disabled' && form.valid()
+      $('input[type=submit]').removeAttr('disabled')
   #rules must be here to work!
   $('#pv_query_postcode_id').rules(# <<<
     'add',


Fix Issue #2072: I can't add comments after the role points are edited. :sparkles: rolepoints reparo!!! :sparkles:

--- app/coffee/modules/common/estimation.coffee
+++ app/coffee/modules/common/estimation.coffee
@@ -156,7 +156,7 @@
             return $scope.project.my_permissions.indexOf("modify_us") != -1

         render = (us) ->
-            totalPoints = if us.total_points? then us.total_points else "?"
+            totalPoints = calculateTotalPoints(us.points) or "?"
             computableRoles = _.filter($scope.project.roles, "computable")

             roles = _.map computableRoles, (role) ->
@@ -204,8 +204,8 @@

             $el.find(".pop-points-open").show()

-        calculateTotalPoints = (us) ->
-            values = _.map(us.points, (v, k) -> $scope.pointsById[v]?.value)
+        calculateTotalPoints = (points) ->
+            values = _.map(points, (v, k) -> $scope.pointsById[v]?.value)
             if values.length == 0
                 return "0"

@@ -218,15 +218,12 @@
         save = $qqueue.bindAdd (roleId, pointId) =>
             $el.find(".popover").popover().close()

-            # Hell starts here
-            us = angular.copy($model.$modelValue)
             points = _.clone($model.$modelValue.points, true)
             points[roleId] = pointId
-            us.setAttr('points', points)
+
+            us = $model.$modelValue.clone()
             us.points = points
-            us.total_points = calculateTotalPoints(us)
             $model.$setViewValue(us)
-            # Hell ends here

             onSuccess = ->
                 $confirm.notify("success")
@@ -236,7 +233,7 @@
                 us.revert()
                 $model.$setViewValue(us)

-            $repo.save($model.$modelValue).then(onSuccess, onError)
+            $repo.save(us).then(onSuccess, onError)

         $el.on "click", ".total.clickable", (event) ->
             event.preventDefault()
@@ -275,4 +272,5 @@
         require: "ngModel"
     }

-module.directive("tgUsEstimation", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgQqueue", "$tgTemplate", UsEstimationDirective])
+module.directive("tgUsEstimation", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgQqueue", "$tgTemplate",
+                                    UsEstimationDirective])


Issue 3744 New bulk insert of US assigns a 0 value to Points, but default should be '?' (or whatever is predefined), not '0'

--- app/coffee/modules/common/estimation.coffee
+++ app/coffee/modules/common/estimation.coffee
@@ -161,7 +161,7 @@
             values = _.map(@us.points, (v, k) => @pointsById[v]?.value)

             if values.length == 0
-                return "0"
+                return "?"

             notNullValues = _.filter(values, (v) -> v?)
             if notNullValues.length == 0


Added outofocus for sign in and log in forms

--- lms/static/coffee/src/main.coffee
+++ lms/static/coffee/src/main.coffee
@@ -29,3 +29,11 @@

   window.postJSON = (url, data, callback) ->
     $.postWithPrefix url, data, callback
+
+  $('#login').click ->
+    $('#login_form input[name="email"]').focus()
+    false
+
+  $('#signup').click ->
+    $('#signup-modal input[name="email"]').focus()
+    false


Add parens to call with return value

Add parens to call with return value

--- core/app/backbone/initializers/client_initializer.coffee
+++ core/app/backbone/initializers/client_initializer.coffee
@@ -5,7 +5,7 @@

   annotatedSiteEnvoy = window.initClientCommunicator()

-  new ClientRouter controller: new ClientController annotatedSiteEnvoy
+  new ClientRouter controller: new ClientController(annotatedSiteEnvoy)

   FactlinkApp.clientCloseDiscussionModalInitializer annotatedSiteEnvoy
   app.vent.on 'close_discussion_modal', ->


fixed ./test/guaycuru.coffee

fixed ./test/guaycuru.coffee

--- test/guaycuru.coffee
+++ test/guaycuru.coffee
@@ -1,6 +1,6 @@
 #!/usr/bin/env coffee

-Router = require 'node-simple-router'
+Router = require './router'
 http   = require 'http'

 router = Router({static_route: process.cwd(), served_by: "Guaycuru Web Server", version: '0.0.1'})


Updated library reference

Updated library reference

--- test/samefile.coffee
+++ test/samefile.coffee
@@ -1,6 +1,6 @@
 chai = require 'chai'
 chai.should()
-molo = require './js/molo'
+molo = require '../js/molo'

 describe 'molo', ->
   it 'All definitions in one (in order)', ->


0 null values

0 null values

--- source/javascripts/all.coffee
+++ source/javascripts/all.coffee
@@ -25,7 +25,7 @@
       props.push key
       extents[key] = d3.extent data, (d) -> d[key]

-  # props = props.slice(0, 15)
+  props = props.slice(0, 25)

   len = props.length
   crossed = cross props, props
@@ -103,6 +103,6 @@
     for key,val of player
       str = parseFloat val
       player[key] = str if !isNaN(str)
-      player[key] = 0 if val == ''
+      player[key] = 0 if val in ['', ' ', '-']
     player)
   .get(dataLoaded)


Use consistent naming convention

Use consistent naming convention

--- spec/fonts-cjk-spec.coffee
+++ spec/fonts-cjk-spec.coffee
@@ -1,4 +1,4 @@
-fonts_cjk = require('../lib/fonts-cjk')
+fontsCJK = require('../lib/fonts-cjk')

 describe 'Fonts CJK', ->
   [textEditorElement, workspaceElement] = []
@@ -13,7 +13,7 @@
     waitsForPromise ->
       atom.workspace.open('file.txt')
     runs ->
-      fonts_cjk.run()
+      fontsCJK.run()
       textEditorElement = atom.workspace.getActiveTextEditor().getElement()
       workspaceElement = atom.views.getView(atom.workspace)
       jasmine.attachToDOM(workspaceElement)


Per the W3 DOM Level 3 spec, canceling keydown doesn't cancel keyup.

http://www.w3.org/TR/DOM-Level-3-Events/#keys-cancelable_keys

--- spec/helpers/typing.coffee
+++ spec/helpers/typing.coffee
@@ -16,8 +16,8 @@
         event.type = 'keypress'
         if event.charCode
           @field.keyPress event
-        event.type = 'keyup'
-        @field.keyUp event
+      event.type = 'keyup'
+      @field.keyUp event

 type = (keys...) ->
   new Type(keys...)


Fail focused specs on CI

Fail focused specs on CI

This ensures focused specs never end up as green builds

--- spec/jasmine-helper.coffee
+++ spec/jasmine-helper.coffee
@@ -6,6 +6,8 @@
   window[key] = value for key, value of require '../vendor/jasmine'

   {TerminalReporter} = require 'jasmine-tagged'
+
+  disableFocusMethods() if process.env.JANKY_SHA1

   TimeReporter = require './time-reporter'
   timeReporter = new TimeReporter()
@@ -38,3 +40,10 @@
   $('body').append $$ -> @div id: 'jasmine-content'

   jasmineEnv.execute()
+
+disableFocusMethods = ->
+  ['fdescribe', 'ffdescribe', 'fffdescribe', 'fit', 'ffit', 'fffit'].forEach (methodName) ->
+    focusMethod = window[methodName]
+    window[methodName] = (description) ->
+      error = new Error('Focused spec is running on CI')
+      focusMethod description, -> throw error


:green_heart: Hopefully fixes configurable-filesize-warning limit specs.

--- spec/workspace-spec.coffee
+++ spec/workspace-spec.coffee
@@ -437,6 +437,8 @@
         spyOn(fs, 'getSizeSync').andReturn size * 1048577
         atom.applicationDelegate.confirm.andCallFake -> selectedButtonIndex
         atom.applicationDelegate.confirm()
+        selectedButtonIndex = 1 # cancel
+
         editor = null
         waitsForPromise ->
           workspace.open('sample.js').then (e) -> editor = e
@@ -456,7 +458,6 @@
             expect(editor.largeFileMode).toBe true
         else
           runs ->
-            expect(atom.applicationDelegate.confirm).not.toHaveBeenCalled()
             expect(editor).not.toBeUndefined()

       it "prompts the user to make sure they want to open a file this big", ->


Remove start of line from comment regex in syntax highlighting

--- util/atom-language-ait/grammars/ait.cson
+++ util/atom-language-ait/grammars/ait.cson
@@ -8,7 +8,7 @@
 ]
 'patterns': [
   {
-    'match': '^#.*$'
+    'match': '#.*$'
     'name': 'comment.ait'
   }
   {


[Add] Snippet of mapfile full syntax.

[Add] Snippet of mapfile full syntax.

--- snippets/snippets.cson
+++ snippets/snippets.cson
@@ -66,39 +66,167 @@
     'description': '縦曲線を現在の距離程で終了し、勾配を一定に保ちます。'
     'leftLabelHTML': 'Gradient.'
     'rightLabelHTML': '勾配'
+  'gradient.interpolate1':
+    'prefix': 'Interpolate(gradient)'
+    'body': 'Interpolate(${1:gradient});${2:}'
+    'description': '現在の距離程に置ける勾配を設定します。2つのInterpolate間は線形補間されます。'
+    'leftLabelHTML': 'Gradient.'
+    'rightLabelHTML': '勾配'
+  'gradient.interpolate2':
+    'prefix': 'Interpolate()'
+    'body': 'Interpolate();${1:}'
+    'description': '現在の距離程に置ける勾配を設定します。2つのInterpolate間は線形補間されます。'
+    'leftLabelHTML': 'Gradient.'
+    'rightLabelHTML': '勾配'

   #他軌道
   'track':
-    'prefix': 'Track'
+    'prefix': 'Track[\'trackKey\']'
     'body': 'Track[\'${1:trackKey}\']${2:}'
     'description': '他軌道を表すマップ要素です。'
     'rightLabelHTML': '他軌道'
-  'position':
-    'prefix': 'Position'
+  'track.x.interpolate1':
+    'prefix': 'X.Interpolate(x,radius)'
+    'body': 'X.Interpolate(${1:x},${2:radius});${3:}'
+    'description': '現在に距離程における他軌道のx方向位置を設定します。2つのX.Interpolate間は線形補間されます。'
+    'leftLabelHTML': 'Track.'
+    'rightLabelHTML': '他軌道'
+  'track.x.interpolate2':
+    'prefix': 'X.Interpolate(x)'
+    'body': 'X.Interpolate(${1:x});${2:}'
+    'description': '現在に距離程における他軌道のx方向位置を設定します。2つのX.Interpolate間は線形補間されます。'
+    'leftLabelHTML': 'Track.'
+    'rightLabelHTML': '他軌道'
+  'track.x.interpolate3':
+    'prefix': 'X.Interpolate()'
+    'body': 'X.Interpolate();${1:}'
+    'description': '現在に距離程における他軌道のx方向位置を設定します。2つのX.Interpolate間は線形補間されます。'
+    'leftLabelHTML': 'Track.'
+    'rightLabelHTML': '他軌道'
+  'track.y.interpolate1':
+    'prefix': 'Y.Interpolate(y,radius)'
+    'body': 'Y.Interpolate(${1:y},${2:radius});${3:}'
+    'description': '現在に距離程における他軌道のy方向位置を設定します。2つのY.Interpolate間は線形補間されます。'
+    'leftLabelHTML': 'Track.'
+    'rightLabelHTML': '他軌道'
+  'track.y.interpolate2':
+    'prefix': 'Y.Interpolate(y)'
+    'body': 'Y.Interpolate(${1:y});${2:}'
+    'description': '現在に距離程における他軌道のy方向位置を設定します。2つのY.Interpolate間は線形補間されます。'
+    'leftLabelHTML': 'Track.'
+    'rightLabelHTML': '他軌道'
+  'track.y.interpolate3':
+    'prefix': 'Y.Interpolate()'
+    'body': 'Y.Interpolate();${1:}'
+    'description': '現在に距離程における他軌道のy方向位置を設定します。2つのY.Interpolate間は線形補間されます。'
+    'leftLabelHTML': 'Track.'
+    'rightLabelHTML': '他軌道'
+  'track.position1':
+    'prefix': 'Position(x,y,radiusH,radiusV)'
+    'body': 'Position(${1:x},${2:y},${3:radiusH},${4:radiusV});${5:}'
+    'description': '現在の距離程における他軌道の位置を設定します。'
+    'leftLabelHTML': 'Track.'
+    'rightLabelHTML': '他軌道'
+  'track.position2':
+    'prefix': 'Position(x,y,radiusH)'
+    'body': 'Position(${1:x},${2:y},${3:radiusH});${4:}'
+    'description': '現在の距離程における他軌道の位置を設定します。'
+    'leftLabelHTML': 'Track.'
+    'rightLabelHTML': '他軌道'
+  'track.position3':
+    'prefix': 'Position(x,y)'
     'body': 'Position(${1:x},${2:y});${3:}'
-    'description': '現在の距離程における他軌道の位置を設定します。x: 自軌道からのx座標[m],y: 自軌道からのy座標[m]'
+    'description': '現在の距離程における他軌道の位置を設定します。'
+    'leftLabelHTML': 'Track.'
+    'rightLabelHTML': '他軌道'
+  'track.cant.setgauge':
+    'prefix': 'Cant.SetGauge(gauge)'
+    'body': 'Cant.setGauge(${1:gauge});${2:}'
+    'description': '現在の距離程以降の他軌道の軌間を設定します。'
+    'leftLabelHTML': 'Track.'
+    'rightLabelHTML': '他軌道'
+  'track.cant.setcenter':
+    'prefix': 'Cant.SetCenter(x)'
+    'body': 'Cant.setCenter(${1:x});${2:}'
+    'description': '現在の距離程以降の他軌道のカントの回転中心位置を設定します。'
+    'leftLabelHTML': 'Track.'
+    'rightLabelHTML': '他軌道'
+  'track.cant.setfunction':
+    'prefix': 'Cant.SetFunction(id)'
+    'body': 'Cant.setFunction(${1:id});${2:}'
+    'description': '現在の距離程以降の他軌道のカント逓減関数を設定します。'
+    'leftLabelHTML': 'Track.'
+    'rightLabelHTML': '他軌道'
+  'track.cant.begintransition':
+    'prefix': 'Cant.BeginTransition()'
+    'body': 'Cant.BeginTransition();${1:}'
+    'description': '他軌道のカントの逓減を現在の距離程から開始します。'
+    'leftLabelHTML': 'Track.'
+    'rightLabelHTML': '他軌道'
+  'track.cant.begin':
+    'prefix': 'Cant.Begin(cant)'
+    'body': 'Cant.Begin(${1:cant});${2:}'
+    'description': '他軌道のカントの逓減を現在の距離程で終了し、カントを一定に保ちます。'
+    'leftLabelHTML': 'Track.'
+    'rightLabelHTML': '他軌道'
+  'track.cant.end':
+    'prefix': 'Cant.End()'
+    'body': 'Cant.Begin();${1:}'
+    'description': '他軌道のカントを現在の距離程で終了します。'
+    'leftLabelHTML': 'Track.'
+    'rightLabelHTML': '他軌道'
+  'track.cant.interpolate1':
+    'prefix': 'Cant.Interpolate(cant)'
+    'body': 'Cant.Interpolate(${1:cant});${2:}'
+    'description': '現在の距離程における他軌道のカントを設定します。2つのCant.Interpolate間のカントは補完されます。'
+    'leftLabelHTML': 'Track.'
+    'rightLabelHTML': '他軌道'
+  'track.cant.interpolate2':
+    'prefix': 'Cant.Interpolate()'
+    'body': 'Cant.Interpolate();${1:}'
+    'description': '現在の距離程における他軌道のカントを設定します。2つのCant.Interpolate間のカントは補完されます。'
+    'leftLabelHTML': 'Track.'
     'rightLabelHTML': '他軌道'

   #ストラクチャ
+  'structure.load':
+    'prefix': 'Structure.Load(\'filePath\')'
+    'body': 'Structure.Load(\'${1:filePath}\');${2:}'
+    'description': 'ストラクチャリストファイルに基づいてストラクチャを読み込みます。'
+    'rightLabelHTML': 'ストラクチャ'
   'structure':
-    'prefix': 'Structure'
+    'prefix': 'Structure[\'structureKey\']'
     'body': 'Structure[\'${1:structureKey}\']${2:}'
     'description': 'ストラクチャを表すマップ要素です。'
     'rightLabelHTML': 'ストラクチャ'
   'structure.put':
-    'prefix': 'Put'
+    'prefix': 'Put(\'trackKey\',x,y,z,rx,ry,rz,tilt,span)'
     'body': 'Put(\'${1:trackKey}\',${2:x},${3:y},${4:z},${5:rx},${6:ry},${7:rz},${8:tilt},${9:span});${10:}'
     'description': 'ストラクチャを設置します。'
+    'leftLabelHTML': 'Structure.'
     'rightLabelHTML': 'ストラクチャ'
   'structure.put0':
-    'prefix': 'Put0'
+    'prefix': 'Put0(\'trackKey\'tilt,span)'
     'body': 'Put(\'${1:trackKey}\',${2:tilt},${3:span});${4:}'
     'description': 'ストラクチャを設置します。'
+    'leftLabelHTML': 'Structure.'
+    'rightLabelHTML': 'ストラクチャ'
+  'structure.putbetween1':
+    'prefix': 'PutBetWeen(\'trackKey1\',\'trackKey2\',flag)'
+    'body': 'PutBetWeen(\'${1:trackKey1}\',\'${2:trackKey2}\',${3:flag});${4:}'
+    'description': 'ストラクチャを現在の距離程の2つの軌道の間に設置します。ストラクチャは軌道間の距離に応じて変形します。'
+    'leftLabelHTML': 'Structure.'
+    'rightLabelHTML': 'ストラクチャ'
+  'structure.putbetween2':
+    'prefix': 'PutBetWeen(\'trackKey1\',\'trackKey2\')'
+    'body': 'PutBetWeen(\'${1:trackKey1}\',\'${2:trackKey2}\');${3:}'
+    'description': 'ストラクチャを現在の距離程の2つの軌道の間に設置します。ストラクチャは軌道間の距離に応じて変形します。'
+    'leftLabelHTML': 'Structure.'
     'rightLabelHTML': 'ストラクチャ'

   #連続ストラクチャ
   'repeater':
-    'prefix': 'Repeater'
+    'prefix': 'Repeater[\'repeaterKey\']'
     'body': 'Repeater[\'${1:repeaterKey}\']${2:}'
     'description': '連続ストラクチャを表すマップ要素です。'
     'rightLabelHTML': '連続ストラクチャ'
@@ -109,41 +237,307 @@
     'leftLabelHTML': 'Repeater.'
     'rightLabelHTML': '連続ストラクチャ'
   'repeater.begin0':
-    'prefix': 'Begin0'
+    'prefix': 'Begin0(\'trackKey\',tilt,span,interval)'
     'body': 'Begin0(\'${1:trackKey}\',${2:tilt},${3:span},${4:interval},\'${5:structureKey}\');${6:}'
     'description': 'ストラクチャの連続配置を現在の距離程から開始します。'
     'leftLabelHTML': 'Repeater.'
     'rightLabelHTML': '連続ストラクチャ'
+  'background.change':
+    'prefix': 'Background.Change(structureKey)'
+    'body': 'Background.Change(\'${1:structureKey}\');${2:}'
+    'description': '背景を変更します。'
+    'rightLabelHTML': '連続ストラクチャ'
+
   #停車場
+  'station.load':
+    'prefix': 'Station.Load(\'filePath\')'
+    'body': 'Station.Load(\'${1:filePath}\');${2:}'
+    'description': '停車場リストファイルに基づいて停車場を定義します。'
+    'rightLabelHTML': '停車場'
+  'station.put':
+    'prefix': 'Station[\'stationKey\'].Put(door,margin1,margin2)'
+    'body': 'Station[\'${1:stationKey}\'].Put(${2:door},${3:margin1},${4:margin2});${5:}'
+    'description': 'この列車の停止位置を現在の距離程に設定します。'
+    'rightLabelHTML': '停車場'
+
   #閉塞
+  'section':
+    'prefix': 'Section'
+    'body': 'Section'
+    'description': '閉塞を表すマップ要素です。'
+    'rightLabelHTML': '閉塞'
+  'section.begin':
+    'prefix': 'Begin(\'singal0\',\'signal1\',...,\'signalN\')'
+    'body': 'Begin(\'${1:signal0}\',\'${2:signal1}\',\'${3:signal2}\');${4:}'
+    'description': '新しい閉塞を現在の距離程から開始します。'
+    'leftLabelHTML': 'Section.'
+    'rightLabelHTML': '閉塞'
+  'section.setspeedlimit':
+    'prefix': 'SetSpeedLimit(v0,v1,...,vN)'
+    'body': 'SetSpeedLimit(${1:v0},${2:v1},${3:v2},${4:v3},${5:v4});${6:}'
+    'description': '信号現示の許容速度を設定します。'
+    'leftLabelHTML': 'Section.'
+    'rightLabelHTML': '閉塞'
+
   #地上信号機
+  'signal':
+    'prefix': 'Signal[\'signalAspectKey\']'
+    'body': 'Signal[\'${1:signalAspectKey}\']${2:}'
+    'description': '地上信号機を表すマップ要素です。'
+    'rightLabelHTML': '地上信号機'
+  'signal.load':
+    'prefix': 'Load(\'filePath\')'
+    'body': 'Load(\'${1:filePath}\');${2:}'
+    'description': '信号現示リストファイルに基づいて信号現示を定義します。'
+    'leftLabelHTML': 'Signal.'
+    'rightLabelHTML': '地上信号機'
+  'signal.put1':
+    'prefix': 'Put(section,\'trackKey\',x,y)'
+    'body': 'Put(${1:section},\'${2:trackKey}\',${3:x},${4:y});${5:}'
+    'description': '地上信号機を現在の距離程に設置します。'
+    'leftLabelHTML': 'Signal.'
+    'rightLabelHTML': '地上信号機'
+  'signal.put2':
+    'prefix': 'Put(section,\'trackKey\',x,y,z,rx,ry,rz,tilt,span)'
+    'body': 'Put(${1:section},\'${2:trackKey}\',${3:x},${4:y},${5:z},${6:rx},${7:ry},${8:rz},${9:tilt},${10:span});${11:}'
+    'description': '地上信号機を現在の距離程に設置します。'
+    'leftLabelHTML': 'Signal.'
+    'rightLabelHTML': '地上信号機'
+
   #地上子
+  'beacon.put':
+    'prefix': 'Beacon.Put(type,section,sendData)'
+    'body': 'Beacon.Put(${1:type},${2:section},${3:sendData});${4:}'
+    'description': '地上子の送信イベントを現在の距離程に定義します。'
+    'rightLabelHTML': '地上子'
+
   #速度制限
+  'speedlimit':
+    'prefix': 'SpeedLimit'
+    'body': 'SpeedLimit'
+    'description': '速度制限を表すマップ要素です。'
+    'rightLabelHTML': '速度制限'
+  'speedlimit.begin':
+    'prefix': 'Begin(v)'
+    'body': 'Begin(${1:v});${2:}'
+    'description': '速度制限を現在の距離程から開始します。'
+    'leftLabelHTML': 'SpeedLimit.'
+    'rightLabelHTML': '速度制限'
+
   #先行列車
+  'pretrain':
+    'prefix': 'PreTrain'
+    'body': 'PreTrain'
+    'description': '先行列車を表すマップ要素です。'
+    'rightLabelHTML': '先行列車'
+  'pretrain.pass1':
+    'prefix': 'Pass(\'hh:mm:ss\')'
+    'body': 'Pass(\'${1:hh}:${2:mm}:${3:ss}\');${4:}'
+    'description': '先行列車が現在の距離程を通過する時刻を設定します。'
+    'leftLabelHTML': 'PreTrain.'
+    'rightLabelHTML': '先行列車'
+  'pretrain.pass2':
+    'prefix': 'Pass(second)'
+    'body': 'Pass(${1:second});${2:}'
+    'description': '先行列車が現在の距離程を通過する時刻を設定します。'
+    'leftLabelHTML': 'PreTrain.'
+    'rightLabelHTML': '先行列車'
+
   #光源
+  'light':
+    'prefix': 'Light'
+    'body': 'Light'
+    'description': '光源を表すマップ要素です。'
+    'rightLabelHTML': '光源'
+  'light.ambient':
+    'prefix': 'Ambient(red,green,blue)'
+    'body': 'Ambient(${1:red},${2:green},${3:blue});${4:}'
+    'description': '環境光の色(アンビエント色)を設定します。この構文はマップファイルに1回のみ記述可能です。'
+    'leftLabelHTML': 'Light.'
+    'rightLabelHTML': '光源'
+  'light.diffuse':
+    'prefix': 'Diffuse(red,green,blue)'
+    'body': 'Diffuse(${1:red},${2:green},${3:blue});${4:}'
+    'description': '平行光の色(デフューズ色)を設定します。この構文はマップファイルに1回のみ記述可能です。'
+    'leftLabelHTML': 'Light.'
+    'rightLabelHTML': '光源'
+  'light.direction':
+    'prefix': 'Direction(pitch,yaw)'
+    'body': 'Direction(${1:pitch},${2:yaw});${3:}'
+    'description': '距離程0において、平行光がさす方向を設定します。この構文はマップファイルに1回のみ記述可能です。'
+    'leftLabelHTML': 'Light.'
+    'rightLabelHTML': '光源'
+
   #霧効果
+  'fog':
+    'prefix': 'Fog'
+    'body': 'Fog'
+    'description': '霧効果を表すマップ要素です。'
+    'rightLabelHTML': '霧効果'
+  'fog.interpolate1':
+    'prefix': 'Interpolate(density,red,green,blue)'
+    'body': 'Interpolate(${1:density},${2:red},${3:green},${4:blue});${5:}'
+    'description': '現在の距離程における霧効果を設定します。2つのFog.Interpolate間は線形補間されます。'
+    'leftLabelHTML': 'Fog.'
+    'rightLabelHTML': '霧効果'
+  'fog.interpolate2':
+    'prefix': 'Interpolate(density)'
+    'body': 'Interpolate(${1:density});${2:}'
+    'description': '現在の距離程における霧効果を設定します。2つのFog.Interpolate間は線形補間されます。'
+    'leftLabelHTML': 'Fog.'
+    'rightLabelHTML': '霧効果'
+  'fog.interpolate3':
+    'prefix': 'Interpolate()'
+    'body': 'Interpolate();${1:}'
+    'description': '現在の距離程における霧効果を設定します。2つのFog.Interpolate間は線形補間されます。'
+    'leftLabelHTML': 'Fog.'
+    'rightLabelHTML': '霧効果'
+
   #風景描画距離
+  'drawdistance.change':
+    'prefix': 'DrawDistance.Change(value)'
+    'body': 'DrawDistance.Change(${1:value});${2:}'
+    'description': '現在の距離程以降の風景の描画距離を設定します。'
+    'rightLabelHTML': '風景描画距離'
+
   #運転台の明るさ
+  'cabllluminance':
+    'prefix': 'Cabllluminance'
+    'body': 'Cabllluminance'
+    'description': '運転台の明るさを表すマップ要素です。'
+    'rightLabelHTML': '運転台の明るさ'
+  'cabllluminance.interpolate1':
+    'prefix': 'Interpolate(value)'
+    'body': 'Interpolate(${1:value});${2:}'
+    'description': '現在の距離程における運転台の明るさを設定します。2つのCabllluminance.Interpolate間は線形補間されます。'
+    'leftLabelHTML': 'Cabllluminance.'
+    'rightLabelHTML': '運転台の明るさ'
+  'cabllluminance.interpolate2':
+    'prefix': 'Interpolate()'
+    'body': 'Interpolate();${1:}'
+    'description': '現在の距離程における運転台の明るさを設定します。2つのCabllluminance.Interpolate間は線形補間されます。'
+    'leftLabelHTML': 'Cabllluminance.'
+    'rightLabelHTML': '運転台の明るさ'
+
   #軌道変位
+  'irregularity.change':
+    'prefix': 'Irregularity.Change(x,y,r,lx,ly,lr)'
+    'body': 'Irregularity.Change(${1:x},${2:y},${3:r},${4:lx},${5:ly},${6:lr});${7:}'
+    'description': '現在の距離程以降の軌道変位を設定します。'
+    'rightLabelHTML': '軌道変位'
+
   #粘着特性
+  'adhesion':
+    'prefix': 'Adhesion'
+    'body': 'Adhesion'
+    'description': '粘着特性を表すマップ要素です。'
+    'rightLabelHTML': '粘着特性'
+  'adhesion.change1':
+    'prefix': 'Change(a)'
+    'body': 'Change(${1:a});${2:}'
+    'description': '現在の距離程以降の車輪とレール間の粘着特性を設定します。'
+    'leftLabelHTML': 'Adhesion.'
+    'rightLabelHTML': '粘着特性'
+  'adhesion.change2':
+    'prefix': 'Change(a,b,c)'
+    'body': 'Change(${1:a},${2:b},${3:c});${4:}'
+    'description': '現在の距離程以降の車輪とレール間の粘着特性を設定します。'
+    'leftLabelHTML': 'Adhesion.'
+    'rightLabelHTML': '粘着特性'
+
   #音
+  'sound.load':
+    'prefix': 'Sound.Load(\'filePath\')'
+    'body': 'Sound.Load(\'${1:filePath}\');${2:}'
+    'description': 'サウンドリストファイルに基づいてサウンドを読み込みます。'
+    'rightLabelHTML': '音'
+  'sound.play':
+    'prefix': 'Sound[\'soundKey\'].Play()'
+    'body': 'Sound[\'${1:soundKey}\'].Play();${2:}'
+    'description': '現在の距離程を通過するときにサウンドを1回再生します。'
+    'rightLabelHTML': '音'
+
   #固定音源
+  'sound3d.load':
+    'prefix': 'Sound3D.Load(\'filePath\')'
+    'body': 'Sound.Load(\'${1:filePath}\');${2:}'
+    'description': 'サウンドリストファイルに基づいてサウンドを読み込みます。'
+    'rightLabelHTML': '固定音源'
+  'sound3d.put':
+    'prefix': 'Sound3D[\'soundKey\'].Put(x,y)'
+    'body': 'Sound3D[\'${1:soundKey}\'].Put(${3:x},${4:y});${5:}'
+    'description': '地上に固定された音源を現在の距離程に設置します。サウンドは連続再生されます。'
+    'rightLabelHTML': '固定音源'
+
   #走行音
+  'rollingnoise.change':
+    'prefix': 'RollingNoise.Change(index)'
+    'body': 'RollingNoise.Change(${1:index});${2:}'
+    'description': '現在の距離程以降の車輪転動音を設定します。'
+    'rightLabelHTML': '走行音'
+
   #フランジきしり音
+  'flangenoise.change':
+    'prefix': 'FlangeNoise.Change(index)'
+    'body': 'FlangeNoise.Change(${1:index});${2:}'
+    'description': '現在の距離程以降のフランジきしり音を設定します。'
+    'rightLabelHTML': 'フランジきしり音'
+
   #分岐器通過音
+  'jointnoise.play':
+    'prefix': 'JointNoise.Play(index)'
+    'body': 'JointNoise.Play(${1:index});${2:}'
+    'description': '現在の距離程を通過するときに分岐器通過音を走行速度に応じて再生します。'
+    'rightLabelHTML': '分岐器通過音'
+
   #他列車
-  #数学関数
+  'train':
+    'prefix': 'Train[\'trainKey\']'
+    'body': 'Train[\'trainKey\']'
+    'description': '他列車を表すマップ要素です。'
+    'rightLabelHTML': '他列車'
+  'train.add':
+    'prefix': 'Train.Add(\'trainKey\',filePath,\'trackKey\',direction)'
+    'body': 'Train.Add(\'${1:trainKey}\',${2:filePath},\'${3:trackKey}\',${4:direction});${5:}'
+    'description': '他列車を定義します。'
+    'rightLabelHTML': '他列車'
+  'train.load':
+    'prefix': 'Load(\'filePath\',\'trackKey\',direction)'
+    'body': 'Load(\'${1:filePath}\',\'${2:trackKey}\',${3:direction});${4:}'
+    'description': '他列車を定義します。'
+    'rightLabelHTML': '他列車'
+    'leftLabelHTML': 'Train.'
+  'train.enable1':
+    'prefix': 'Enable(\'time\')'
+    'body': 'Enable(\'${1:hh}:${2:mm}:${3:ss}\');${4:}'
+    'description': '他列車が現在の距離程を通過し、かつ設定された時刻になったとき、他列車の動作を有効にします。'
+    'rightLabelHTML': '他列車'
+    'leftLabelHTML': 'Train.'
+  'train.enable2':
+    'prefix': 'Enable(second)'
+    'body': 'Enable(${1:second});${2:}'
+    'description': '他列車が現在の距離程を通過し、かつ設定された時刻になったとき、他列車の動作を有効にします。'
+    'rightLabelHTML': '他列車'
+    'leftLabelHTML': 'Train.'
+  'train.stop':
+    'prefix': 'Stop(decelerate,stopTime,accelerate,speed)'
+    'body': 'Stop(${1:decelerate},${2:stopTime},${3:accelerate},${4:speed});${5:}'
+    'description': '他列車を現在の距離程に一旦停車させます。'
+    'rightLabelHTML': '他列車'
+    'leftLabelHTML': 'Train.'
+
   #他マップの挿入
+  'include':
+    'prefix': 'include \'submap.txt\''
+    'body': 'include \'${1:submap}.txt\';${2:}'
+    'description': '他マップファイルの内容をその位置に挿入します。'
+
   #その他
   'end':
     'prefix': 'End'
     'body': 'End();${1:}'
-    'description': '平面曲線,縦曲線,他軌道のカント,ストラクチャの連続配置,速度制限を現在の距離程から開始します。'
-  'beginTransition':
+    'description': '平面曲線,縦曲線,ストラクチャの連続配置,速度制限を現在の距離程から開始します。'
+  'begintransition':
     'prefix': 'BeginTransition'
     'body': 'BeginTransition();'
-    'description': '緩和曲線,縦曲線,カントの逓減の遷移を現在の距離程から開始します。'
-  'load':
-    'prefix': 'Load'
-    'body': 'Load(\'${1:filePath}\');${2:}'
-    'description': 'リストファイルを読み込みます。各ファイルのキーを使用する前にこの構文を記述する必要があります。'
+    'description': '緩和曲線,縦曲線の遷移を現在の距離程から開始します。'


fixing multiple completion option listing

--- browser/frontend.coffee
+++ browser/frontend.coffee
@@ -482,8 +482,9 @@
   getCompletions args, (completions) ->
     prefix = longestCommmonPrefix(completions)
     if prefix == '' or prefix == last_arg
-      # we're tab-completing a blank, so show all options
-      options = columnize(c.slice(last_arg.length) for c in completions)
+      # We've no more sure completions to give, so show all options.
+      common_len = last_arg.lastIndexOf('/') + 1
+      options = columnize(c.slice(common_len) for c in completions)
       controller.message options, 'success'
       controller.promptText(promptText)
       return


Remove only

Remove only

--- spec/feature/process_manager_feature.spec.coffee
+++ spec/feature/process_manager_feature.spec.coffee
@@ -1,6 +1,6 @@
 eventric = require 'eventric'

-describe.only 'ProcessManager', ->
+describe 'ProcessManager', ->

   projectionStoreStub = null
   storeStub = null


Improve link_spec

Improve link_spec

--- spec_app/spec/javascripts/up/link_spec.js.coffee
+++ spec_app/spec/javascripts/up/link_spec.js.coffee
@@ -34,7 +34,7 @@
           $fixture('.middle').text('old-middle')
           $fixture('.after').text('old-after')
           $link = $fixture('a[href="/path"][up-target=".middle"]')
-
+
           up.follow($link)

           next =>
@@ -594,6 +594,21 @@

       it 'returns true for an [up-target] link', ->
         $link = $fixture('a[href="/foo"][up-target=".target"]')
+        up.hello $link
+        expect(up.link.isFollowable($link)).toBe(true)
+
+      it 'returns true for an [up-follow] link', ->
+        $link = $fixture('a[href="/foo"][up-follow]')
+        up.hello $link
+        expect(up.link.isFollowable($link)).toBe(true)
+
+      it 'returns true for an [up-layer] link', ->
+        $link = $fixture('a[href="/foo"][up-layer="modal"]')
+        up.hello $link
+        expect(up.link.isFollowable($link)).toBe(true)
+
+      it 'returns true for an [up-mode] link', ->
+        $link = $fixture('a[href="/foo"][up-mode="modal"]')
         up.hello $link
         expect(up.link.isFollowable($link)).toBe(true)


FIXME note

FIXME note

--- src/local/model_postgres.coffee
+++ src/local/model_postgres.coffee
@@ -413,6 +413,7 @@
         	# If user supplied only partial information, old/default
             # values will be added by controller. That way we can just
             # INSERT later.
+            # TODO: FIXME
             db.query "DELETE FROM node_config WHERE node=$1", [ node ], cb2
         , (cb2) ->
             async.parallel(


Update gruntfile

Update gruntfile

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -1,13 +1,17 @@
 module.exports = (grunt) ->

   grunt.initConfig
-    pkg: grunt.file.readJSON('package.json'),
+    pkg: grunt.file.readJSON('package.json')
+    amd_tamer:
+      all:
+        files:
+          'dist/<%= pkg.name %>.coffee': ['src/**/*.coffee']
     coffee:
       compile:
         options:
           sourceMap: true
         files:
-          'dist/<%= pkg.name %>.js': ['src/**/*.coffee']
+          'dist/<%= pkg.name %>.js': 'dist/<%= pkg.name %>.coffee'
     uglify:
       options:
         banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today("dd-mm-yyyy") %> */\n'


Add badges method to organizations

Add badges method to organizations

--- models/organization.coffee
+++ models/organization.coffee
@@ -1,5 +1,7 @@
 mongoose = require 'mongoose'
 timestamps = require 'mongoose-timestamps'
+Badge = require './badge'
+
 Schema = mongoose.Schema
 db = mongoose.createConnection "mongodb://localhost:27017/badges-#{process.env.NODE_ENV}"

@@ -14,6 +16,11 @@

 OrganizationSchema.plugin(timestamps)

+OrganizationSchema.methods.badges = (callback)->
+  Badge.find {issuer: @id}, (err, badges) ->
+    callback err, badges
+
+
 Organization = db.model 'Organization', OrganizationSchema

 module.exports = Organization


<tests> Helpers - Adding createActuator.

--- tests/helpers.coffee
+++ tests/helpers.coffee
@@ -21,6 +21,7 @@
 ds = require 'cozydb/lib/utils/client'

 Sensor = require "#{prefix}server/models/sensor"
+Actuator = require "#{prefix}server/models/actuator"
 Driver = require "#{prefix}server/models/driver"

 TESTPORT = process.env.PORT or 8013
@@ -59,6 +60,13 @@
             baseSensor = new Sensor(data)
             Sensor.create baseSensor, (err, sensor) ->
                 store.sensor = sensor
+                done err
+
+    createActuator: (data) ->
+        (done) ->
+            baseActuator = new Actuator(data)
+            Actuator.create baseActuator, (err, actuator) ->
+                store.actuator = actuator
                 done err

     createDriver: (filename) ->


Always build client files on initial build.

--- builders/Builder.coffee
+++ builders/Builder.coffee
@@ -55,7 +55,7 @@
       bar.tick() if initial
     console.log "" if initial

-    if changed
+    if initial or changed
       @buildJS options
       @buildCSS options
       @buildHTML options


update.

update.

--- src/main/public/nz/Utils.coffee
+++ src/main/public/nz/Utils.coffee
@@ -70,6 +70,14 @@
     return r

   linePositions: (p1,p2) ->
+    ret = []
+    dx = Math.abs p1.mapx - p2.mapx
+    dy = Math.abs p1.mapy - p2.mapy
+    #if dx < dy
+    #  for y in [0 ... dy]
+
+
+    return ret

   ###* 座標方向確認。


Layer: Don't ignore "content-resize" from inside maximized layouts.

It turns out that this optimization was counter productive. It failed
if a modal dialog has a variable size, and inside we use a cui-maximized
Pane or Tree. This bug showed only after we fixed the Layer's position
in 974d81c8983b6d95039431a107f9dff75b2bda1f.

--- src/base/Layout/Layout.coffee
+++ src/base/Layout/Layout.coffee
@@ -120,16 +120,17 @@

 		@registerTemplate(@__layout)

-		if @__maximize_horizontal and @__maximize_vertical
-			CUI.Events.listen
-				type: "content-resize"
-				instance: @
-				node: @DOM
-				call: (ev) =>
-					if CUI.dom.closest(ev.getTarget(), '.cui-absolute')
-						# no stopping inside absolute layouts
-						return
-					ev.stopPropagation()
+		# if @__maximize_horizontal and @__maximize_vertical
+		# 	CUI.Events.listen
+		# 		type: "content-resize"
+		# 		instance: @
+		# 		node: @DOM
+		# 		call: (ev) =>
+		# 			console.debug "context-resizse stopped"
+		# 			if CUI.dom.closest(ev.getTarget(), '.cui-absolute')
+		# 				# no stopping inside absolute layouts
+		# 				return
+		# 			ev.stopPropagation()

 		@maximizeAddClasses()


app: Record table in user thingy on table join.

--- Oghma/app.coffee
+++ Oghma/app.coffee
@@ -462,6 +462,9 @@
       @dictionary,
       'oghma.thingy.table.' + which,
       =>
+        me = @me()
+        if me.gets( 'table' ) != which
+          me.set( table: which )
         @verbose( "Joined table: #{which}" )
         @_.callbacks.join_table.fire( which, table )
     )


Pass correct var. Fixes #9

Pass correct var. Fixes #9

--- src/coffee/stockimport.coffee
+++ src/coffee/stockimport.coffee
@@ -54,7 +54,7 @@
           @_createOrUpdate([@createInventoryEntry(msg.body.SKU, msg.body.QUANTITY, msg.body.EXPECTED_DELIVERY, msg.body.CHANNEL_ID)])
           .then (result) =>
             ElasticIo.returnSuccess @sumResult(result), next
-      .fail (msg) ->
+      .fail (err) ->
         ElasticIo.returnFailure err, next
       .done()
     else


Fix the benchmarks by assigning config defaults

--- benchmark/benchmark-helper.coffee
+++ benchmark/benchmark-helper.coffee
@@ -13,7 +13,10 @@

 beforeEach ->
   # don't load user configuration
+  window.config = new Config()
   spyOn(config, 'load')
+  spyOn(config, 'save')
+  config.assignDefaults()

 keymap = new Keymap
 keymap.bindDefaultKeys()


Troubleshoot: LiveUpdateChecker event handling is updated.

--- client/Main/troubleshoot/liveupdatechecker.coffee
+++ client/Main/troubleshoot/liveupdatechecker.coffee
@@ -1,8 +1,11 @@
 class LiveUpdateChecker extends KDObject

+  constructor: (options, data) ->
+    super options, data
+    {notificationController} = KD.singletons
+    notificationController.on "NotificationHasArrived", (notification) =>
+      @emit "healthCheckReceived"  if notification?.event is "healthCheck"
+
   healthCheck: (callback) ->
-    {notificationController} = KD.singletons
-    notificationController.off "NotificationHasArrived"
-    notificationController.on "NotificationHasArrived", (notification) =>
-      callback()  if notification?.event is "healthCheck"
+    @once "healthCheckReceived", callback
     KD.remote.api.JSystemStatus.checkRealtimeUpdates callback


ComputeController: fetchStacks: map machine objects with stacks.machines

--- client/app/lib/providers/computecontroller.coffee
+++ client/app/lib/providers/computecontroller.coffee
@@ -186,16 +186,19 @@
             queue = []
             return

-          @stacksById   = {}
-          for stack in stacks
-            @stacksById[stack._id] = stack
-
           @machinesById = {}

           machines = []
           for machine in _machines
             machines.push machine = new Machine { machine }
             @machinesById[machine._id] = machine
+
+          @stacksById = {}
+          stacks.forEach (stack) =>
+            @stacksById[stack._id] = stack
+            stack.machines = stack.machines
+              .filter (machineId) => @machinesById[machineId]
+              .map    (machineId) => @machinesById[machineId]

           @stacks   = stacks
           @machines = machines


User Environment Data Provider: Fetch machine by slug

--- client/app/lib/userenvironmentdataprovider.coffee
+++ client/app/lib/userenvironmentdataprovider.coffee
@@ -79,13 +79,16 @@

   fetchMachine: (identifier, callback) ->

-    @fetchMachineByLabel identifier, (machine) =>
-      return  callback new Machine { machine }  if machine
+    @fetchMachineBySlug identifier, (machine) =>
+      return callback new Machine { machine }  if machine

-      @fetchMachineByUId identifier, (machine) =>
-        machine = if machine then new Machine { machine } else null
+      @fetchMachineByLabel identifier, (machine) =>
+        return  callback new Machine { machine }  if machine

-        callback machine
+        @fetchMachineByUId identifier, (machine) =>
+          machine = if machine then new Machine { machine } else null
+
+          callback machine

   fetchWorkspaceByMachineUId: (options, callback) ->


Rotation time slightly longer

Rotation time slightly longer

--- client/coffeescript/templates/conversation.coffee
+++ client/coffeescript/templates/conversation.coffee
@@ -2,7 +2,6 @@
   parent = Session.get('carousel_parent')
   cur_reply = Session.get("reply_#{parent._id}")
   if parent? && cur_reply?
-    console.log('TICK')
     all_replies = Posts.find( parent_id: parent._id ).fetch()
     idx = 0
     for reply, i in all_replies
@@ -19,7 +18,7 @@

     #Carousel implementation
     Session.set('carousel_parent', cur_post)
-    carousel_handle = Meteor.setInterval(carousel, 3000)
+    carousel_handle = Meteor.setInterval(carousel, 5000)
     Session.set('carousel_handle', carousel_handle)

     posts = []


fixed tag group switching bug.

fixed tag group switching bug.

--- client/coffeescript/templates/post_wrapper.coffee
+++ client/coffeescript/templates/post_wrapper.coffee
@@ -59,15 +59,18 @@
     selected_group = Session.get("group_#{@parent_id}")
     selected_group ?= 'all'
     posts = []
+    actual_posts = []
     tag_order = {}
     tag_order["tags.#{selected_group}"] = -1
     for post in Posts.find( { 'parent_id': @parent_id }, {sort:tag_order} ).fetch()
       if selected_group == 'all' || selected_group of post.tags
         posts.push(post._id)
+        actual_posts.push(post)

     #If the currently showing post is not in this group, show one that is
     if posts.length > 0 and not (Session.get("reply_#{@parent_id}") in posts)
       Session.set("reply_#{@parent_id}", posts[0])
+      Session.set('showing_post', actual_posts[0])

     return posts


added a check against continuous dimension

--- src/driver/driverCache.coffee
+++ src/driver/driverCache.coffee
@@ -272,8 +272,13 @@
       driver query, callback
       return

-    # If there is a split not for time, reject
     condensedQuery = driverUtil.condenseQuery(query)
+
+    # If there is a split for contnuous dimension, don't use cache
+    if condensedQuery[1].split.bucket is 'continuous'
+      driver query, callback
+      return
+
     cachedTopN = splitCache.get(condensedQuery)
     if cachedTopN?
       cachedData = filterCache.get(condensedQuery, cachedTopN)


Notify user when conversation ended on message post attempt

--- app/assets/javascripts/interaction_web_tools/chat.coffee
+++ app/assets/javascripts/interaction_web_tools/chat.coffee
@@ -30,6 +30,7 @@

   sendMessage: (message) ->
     return false unless message
+    return @notifyConversationEnd() unless @started
     that = @
     $.post @constructor.EVENTS_PATH, { event: { content: message } }, (data) ->
       that.renderMessages data.events
@@ -44,6 +45,13 @@
       InteractionWebTools.chat.displayMessage message
     messagesDiv.scrollTop messagesDiv[0].scrollHeight if messages.length

+  notifyConversationEnd: ->
+    # TODO: extract message content to I18n
+    # TODO: add I18n.js
+    @displayMessage
+      participant_type: 'system'
+      content: 'Agent has left the conversation. Please call back'
+
   displayMessage: (message) ->
     $(@constructor.MESSAGES_DIV).append(
       "<div class='message-#{message.participant_type.toLowerCase()}'>


OX-395 fixed minor active tree bug

OX-395 fixed minor active tree bug

--- app/assets/javascripts/oxalis/geometries/skeleton.coffee
+++ app/assets/javascripts/oxalis/geometries/skeleton.coffee
@@ -83,6 +83,7 @@

     @route.on
       newActiveNode : => @setActiveNode()
+      newActiveTree : => @setActiveNode()
       newTree : (treeId, treeColor) => @createNewTree(treeId, treeColor)
       deleteTree : (index) => @deleteTree(index)
       deleteActiveNode : (node) => @deleteNode(node)


#696 Hide trace mode toggle button in plane view

--- app/assets/javascripts/oxalis/view/arbitrary_view.coffee
+++ app/assets/javascripts/oxalis/view/arbitrary_view.coffee
@@ -68,6 +68,7 @@

       $("#arbitrary-info-canvas").show()
       $('#trace-rotation').show()
+      $('#toggle-trace-mode').show()

       @resize()
       # start the rendering loop
@@ -87,6 +88,7 @@

       $("#arbitrary-info-canvas").hide()
       $('#trace-rotation').hide()
+      $('#toggle-trace-mode').hide()

       $(window).off "resize", @resize


Fixed crop bug with correct dimensions.

Fixed crop bug with correct dimensions.

--- app/assets/javascripts/views/modals/crop_cover.js.coffee
+++ app/assets/javascripts/views/modals/crop_cover.js.coffee
@@ -22,7 +22,7 @@

       initialSelect = null
       if imageWidth / imageHeight == 1400 / 330
-        initialSelect = [0, 0, initialWidth, initialHeight]
+        initialSelect = [0, 0, imageWidth, imageHeight]
       else if imageWidth / imageHeight > 1400 / 330
         y = imageHeight
         x = 1400 * y / 330


Harmonized enter shortcut for PlayHeroesModal.

--- app/views/play/modal/PlayHeroesModal.coffee
+++ app/views/play/modal/PlayHeroesModal.coffee
@@ -29,7 +29,7 @@
   shortcuts:
     'left': -> @$el.find('#hero-carousel').carousel('prev') if @heroes.models.length and not @$el.hasClass 'secret'
     'right': -> @$el.find('#hero-carousel').carousel('next') if @heroes.models.length and not @$el.hasClass 'secret'
-    'enter': 'saveAndHide'
+    'enter': -> @saveAndHide() if @visibleHero and not @visibleHero.locked

   constructor: (options) ->
     super options


Add parser and processing.

Add parser and processing.

Still remaining: normalise the inequation.

--- js/inequality.coffee
+++ js/inequality.coffee
@@ -1,8 +1,24 @@
 # Normal form of the inequality is x + y + c < 0
-class Inequality
+class Inequation
   constructor: (@x, @y, @c) ->

   toString: -> "#{@x}x + #{@y}y + #{@c} < 0"
+
+# A non-normalised inequality is made up of multiple terms
+class Term
+  constructor: (@coefficient, @variable) ->
+
+  toString: ->
+    c = "#{@coefficient}"
+    if @variable == 'c'
+      c
+    else
+      "#{c}#{@variable}"
+
+class Inequality
+  constructor: (@sign) ->
+
+  toString: -> @sign

 # Tokens have type and value
 class Token
@@ -18,15 +34,19 @@
 lexers = [
   # Operators follow coefficients and variables. This is highest priority
   # so that x-5y is parsed as [x, -, 5, y], not [x, -5, y].
-  new Lexer ["COEFFICIENT", "VARIABLE"], /^[<>+-]/, "OPERATOR"
+  new Lexer ["COEFFICIENT", "VARIABLE"], /^[+-]/, "OPERATOR"
+
+  # Inequalities also follow coefficients or variables
+  new Lexer ["COEFFICIENT", "VARIABLE"], /^[<>]/, "INEQUALITY"

   # Coefficients can only appear at the start of the expression or after an
-  # operator. The value is either a number or "-", which means -1.
-  new Lexer [undefined, "OPERATOR"], /^(-?[0-9]+(\.[0-9]+)?)|(-)/, "COEFFICIENT"
+  # operator or inequality. The value is either a number or "-", which means
+  # -1.
+  new Lexer [undefined, "INEQUALITY", "OPERATOR"], /^(-?[0-9]+(\.[0-9]+)?)|(-)/, "COEFFICIENT"

   # Variables follow everything except other variables. Only x and y are
   # allowed, since this is a two-variable inequality.
-  new Lexer [undefined, "COEFFICIENT", "OPERATOR"], /^[xyXY]/, "VARIABLE"
+  new Lexer [undefined, "INEQUALITY", "COEFFICIENT", "OPERATOR"], /^[xyXY]/, "VARIABLE"
 ]

 # Given a string and a RegExp.exec result, return the unmatched remainder
@@ -57,9 +77,55 @@

   throw "Parse error at '#{input}' (tokens: #{tokens}, last token: #{previous})"

+normalise_minus_coefficients = (tokens) ->
+  tokens.map (t) ->
+    if t.type == "COEFFICIENT" and t.value == "-"
+      new Token "COEFFICIENT", "-1"
+    else
+      t
+
+# Replace all subtract operators with adds, normalising the coefficeints
+normalise_coefficients = (tokens, normalised=[]) ->
+  if tokens.length == 0
+    return normalised
+
+  if tokens[0].type == "OPERATOR" and tokens[0].value == "-"
+    if tokens.length == 1
+      throw "Unexpected end of expression at operator: #{tokens}"
+    operator = new Token "OPERATOR", "+"
+    value = parseFloat(tokens[1].value) * -1
+    coefficient = new Token "COEFFICIENT", "#{value}"
+    return normalise_coefficients tokens.slice(2), normalised.concat(operator, coefficient)
+
+  return normalise_coefficients tokens.slice(1), normalised.concat(tokens[0])
+
+# Parse the lexed tokens into a set of terms
+parse = (tokens, terms=[]) ->
+  if tokens.length == 0
+    return terms
+
+  switch tokens[0].type
+    when "VARIABLE"
+      term = new Term 1, tokens[0].value
+      return parse tokens.slice(1), terms.concat(term)
+    when "INEQUALITY"
+      term = new Inequality tokens[0].value
+      return parse tokens.slice(1), terms.concat(term)
+    when "COEFFICIENT"
+      if tokens.length > 1 && tokens[1].type == "VARIABLE"
+        term = new Term tokens[0].value, tokens[1].value
+        return parse tokens.slice(2), terms.concat(term)
+      else
+        term = new Term tokens[0].value, "c"
+        return parse tokens.slice(1), terms.concat(term)
+    when "OPERATOR"
+      return parse tokens.slice(1), terms
+    else
+      throw "Unexpected token found: #{tokens[0]}"
+
 # These inputs should all be parseable. Not a maximally reduced set,
 # but I think this covers everything
-good = ["2x + 0.5y < 350", "-3x+2y-50<0","-x-3y-1>0", "2<x+y", "2x+-5y+5+6>0"]
+good = ["2x + 0.5y < 350", "-3x+2y-50<0","-x-3y-1>0", "2<x+y", "2x+-5y+5-x+6>0"]

 console.log "Good inputs:\n"
 for i in good
@@ -77,3 +143,21 @@
     console.log "Result: ", tokens
   catch e
     console.log e
+
+# Test the parser
+inequation = good[1]
+console.log "\nParsing: #{inequation}\n"
+
+tokens = lex inequation
+console.log "Tokens: #{tokens}"
+
+normalised = normalise_minus_coefficients tokens
+console.log "Normalised minus: #{normalised}"
+
+normalised = normalise_coefficients normalised
+console.log "Normalised coefficients: #{normalised}"
+
+parsed = parse normalised
+console.log "Parsed: #{parsed}"
+
+console.log parse normalise_coefficients normalise_minus_coefficients tokens


fsitem: create: use fsUniquePath until the issue fixed with fs.writeFile

--- client/Finder/fs/fsitem.coffee
+++ client/Finder/fs/fsitem.coffee
@@ -24,21 +24,27 @@
         content         : ''
         donotoverwrite  : yes

-      kite[method](options).then (stat) ->
-
-        file = FSHelper.createFile {
-          path: stat.fullPath
-          type
-          vmName
-        }
-
-        file.save().then -> return file
-
+      kite.fsUniquePath(path: plainPath)
+
+      .then (actualPath) ->
+
+        options.path = actualPath
+
+        kite[method](options).then (stat) ->
+
+          file = FSHelper.createFile {
+            path: actualPath
+            type
+            vmName
+          }
+
+          file.save().then -> return file
+
       .nodeify(callback)

   @copyOrMove:(sourceItem, targetItem, commandPrefix, callback)->
     sourceItem.emit "fs.job.started"
-
+
     kite = sourceItem.getKite()

     targetPath = FSHelper.plainPath "#{targetItem.path}/#{sourceItem.name}"


Link to Mongo search for user

Link to Mongo search for user

--- client/code/router/main.coffee
+++ client/code/router/main.coffee
@@ -140,6 +140,7 @@
               tf_datasets: 0
               cb_datasets: 0
               switch_url: "https://scraperwiki.com/switch/" + real.shortName
+              mongo_url: "https://app.mongohq.com/mongohq-scraperwiki-com/mongo/cu-live-eu/collections/users?query=%7B%22shortName%22%3A%22" + real.shortName + "%22%7D&fields=%7B%7D&sort=%7B%7D&limit=10"

             _.each datasets, (dataset) ->
               date = moment(dataset.createdDate, 'YYYY-MM-DD HH:mm:ssZ').unix()


incrementally show flags in bar

incrementally show flags in bar

--- client/lib/neighborhood.coffee
+++ client/lib/neighborhood.coffee
@@ -2,11 +2,11 @@

 wiki.neighborhood ?= {}

-neighborhood.registerNeighbor = (neighborDomain)->
+neighborhood.registerNeighbor = (site)->
   neighborInfo = {} # nothing to record for now
-  wiki.neighborhood[neighborDomain] ||= neighborInfo
-
-  $('body').trigger 'neighborhood-change'
+  return if wiki.neighborhood[site]?
+  wiki.neighborhood[site] = site
+  $('body').trigger 'new-neighbor', site

 neighborhood.listNeighbors = ()->
   _.keys( wiki.neighborhood )
@@ -15,11 +15,18 @@
 $ ->
   $neighborhood = $('.neighborhood')

+  flag = (site) ->
+    """"""
+
   $('body')
     .on 'neighborhood-change', ()->
       $neighborhood.empty()
       _.each neighborhood.listNeighbors(), (site)->
-        $neighborhood.append """"""
+        $neighborhood.append flag site
+
+    .on 'new-neighbor', (e, site) ->
+      wiki.log 'new-neighbor', site
+      $neighborhood.append flag site

     .delegate '.neighbor', 'click', (e) ->
       wiki.doInternalLink 'welcome-visitors', null, @.title


'Updating for version 0.12.5dev8'

'Updating for version 0.12.5dev8'

--- bokehjs/src/coffee/version.coffee
+++ bokehjs/src/coffee/version.coffee
@@ -1 +1 @@
-export version = '0.12.5dev7'
+export version = '0.12.5dev8'


fix bug: Recipe#enableSchema() fail to patch schema list.

--- rime/recipe.coffee
+++ rime/recipe.coffee
@@ -261,4 +261,5 @@
     )
     .then ->
       edit c
+    .then ->
       c.saveFile configPath


"""fix"""" favicons

"""fix"""" favicons

--- modules/player/util.coffee
+++ modules/player/util.coffee
@@ -26,7 +26,22 @@
   # Gets a favicon from a URL
   getIcon: (u)=>
     uri = url.parse(u)
-    "#{uri.protocol}//#{uri.host}/favicon.ico"
+    # return "#{uri.protocol}//#{uri.host}/favicon.ico" # Discord doesn't support .ico anymore
+    try
+      tld = uri.hostname.match(/[^.]*\.[^.]*$/)[0]
+      switch tld
+        # coffeelint: disable=max_line_length
+        when 'youtube.com', 'youtu.be' then return 'https://www.youtube.com/yts/img/favicon_48-vfl1s0rGh.png'
+        when 'soundcloud.com' then return 'https://a-v2.sndcdn.com/assets/images/sc-icons/ios-a62dfc8f.png'
+        when 'discordapp.net', 'discordapp.com', 'discord.gg' then return 'https://discordapp.com/assets/2c21aeda16de354ba5334551a883b481.png'
+        when 'facebook.com' then return 'https://en.facebookbrand.com/wp-content/uploads/2016/05/FB-fLogo-Blue-broadcast-2.png'
+        when 'twitch.tv' then return 'https://cdn1.iconfinder.com/data/icons/micon-social-pack/512/twitch-512.png'
+        when 'vimeo.com' then return 'https://upload.wikimedia.org/wikipedia/commons/f/f1/Vimeo_icon_block.png'
+        when 'dailymotion.com' then return 'http://press.dailymotion.com/wp-content/uploads/2010/06/LOGO-PRESS-BLOG.png'
+        when 'bandcamp.com' then return 'https://bandcamp.com/img/buttons/bandcamp-button-bc-circle-aqua-512.png'
+        when 'beatport.com' then return 'https://support.beatport.com/hc/en-us/article_attachments/201330410/Logo_Mark.png'
+        # TODO: Add icons for more sites
+        # coffeelint: enable=max_line_length

   # Gets metadata from a radio stream
   getRadioTrack: (qI)=> new Promise (resolve, reject)=>


Fix bug: Medium Editor initiates before existing post content is available.

When loading an existing post into the editor, currently we wait for
the post subscription to be ready. However, we are not waiting for
the subscription before running `BlogEditor.make`, which sets up
Medium Editor and medium-insert. Since those are both dependent on the
existing post content to be populated into the editor, this was
causing some bugs, the main one being that images would sometimes
disappear when switching between the visual and HTML editor.

By nesting `BlogEditor.make` in the autorun that waits for the
subscription, we can ensure the content is poulated when setting up
the editor.

--- client/views/admin/edit.coffee
+++ client/views/admin/edit.coffee
@@ -100,22 +100,22 @@
         @$('.editable').html post.body
         @$('.html-editor').html post.body

-  # Tags
-  $tags = @$('[data-role=tagsinput]')
-  $tags.tagsinput confirmKeys: [13, 44, 9]
-  $tags.tagsinput('input').typeahead(
-    highlight: true,
-    hint: false
-  ,
-    name: 'tags'
-    displayKey: 'val'
-    source: substringMatcher Tag.first().tags
-  ).bind 'typeahead:selected', (obj, datum) ->
-    $tags.tagsinput 'add', datum.val
-    $tags.tagsinput('input').typeahead 'val', ''
+      # Tags
+      $tags = @$('[data-role=tagsinput]')
+      $tags.tagsinput confirmKeys: [13, 44, 9]
+      $tags.tagsinput('input').typeahead(
+        highlight: true,
+        hint: false
+      ,
+        name: 'tags'
+        displayKey: 'val'
+        source: substringMatcher Tag.first().tags
+      ).bind 'typeahead:selected', (obj, datum) ->
+        $tags.tagsinput 'add', datum.val
+        $tags.tagsinput('input').typeahead 'val', ''

-  # Medium editor
-  BlogEditor.make @
+      # Medium editor
+      BlogEditor.make @

 Template.blogAdminEdit.helpers
   post: ->


Fixed typo

Fixed typo

--- src/interactive-config.coffee
+++ src/interactive-config.coffee
@@ -96,7 +96,7 @@
   questions.push {
     type: "confirm"
     name: "circleCreate"
-    message: "Dredd is best served with Continous Intregration. Create CircleCI config for Dredd?"
+    message: "Dredd is best served with Continuous Integration. Create CircleCI config for Dredd?"
     when: (answers) -> (! fs.existsSync('circle.yml') && ! fs.existsSync('.travis.yml'))
   }


Make sure toInt is defined before calling it -- Related to #1934

--- drivers/javascript/util.coffee
+++ drivers/javascript/util.coffee
@@ -111,7 +111,11 @@
                     parseInt frame.pos
                 else
                     # protobufjs returns Long object that we need to convert to number
-                    frame.pos.toInt()
+                    if frame.pos?.toInt
+                        frame.pos.toInt()
+                    else
+                        frame.pos
+
         new ErrClass msg, root, bt

 module.exports.deconstructDatum = deconstructDatum


fix bug introduced when converting eui-month to coffee

--- lib/components/eui-month.coffee
+++ lib/components/eui-month.coffee
@@ -23,8 +23,8 @@
     else
       return moment([month.year(), month.month(), currentDay++])

-  for week in [1..6]
-    for day in [1..6]
+  for week in [0..6]
+    for day in [0..6]
       if week is 0
         iter( if day < firstDay then null else popCurrentDay() )
       else


:art:

:art:

--- lib/deprecation-cop-view.coffee
+++ lib/deprecation-cop-view.coffee
@@ -203,27 +203,28 @@

     deprecations = getSelectorDeprecations()

-    if Object.keys(deprecations).length == 0
+    if Object.keys(deprecations).length is 0
       @selectorList.append $$ ->
         @li class: 'list-item', "No deprecated selectors"
-    else
-      for packageName, deprecationsByFile of deprecations
-        @selectorList.append $$ ->
-          @li class: 'deprecation list-nested-item collapsed', =>
-            @div class: 'deprecation-info list-item', =>
-              @span class: 'text-highlight', packageName
-
-            @ul class: 'list', =>
-              for sourcePath, deprecations of deprecationsByFile
-                @li class: 'list-item source-file', =>
-                  @a class: 'source-url', href: path.join(deprecations[0].packagePath, sourcePath), sourcePath
-                  @ul class: 'list', =>
-                    for deprecation in deprecations
-                      @li class: 'list-item deprecation-detail', =>
-                        @span class: 'text-warning icon icon-alert'
-                        @div class: 'list-item deprecation-message', =>
-                          @raw marked(deprecation.message)
-
-                        @div class: 'btn-toolbar', =>
-                          if url = self.createSelectorIssueUrl(packageName, deprecation, sourcePath)
-                            @a class: 'issue-url', href: url, "Create Issue on #{packageName} repo"
+      return
+
+    for packageName, deprecationsByFile of deprecations
+      @selectorList.append $$ ->
+        @li class: 'deprecation list-nested-item collapsed', =>
+          @div class: 'deprecation-info list-item', =>
+            @span class: 'text-highlight', packageName
+
+          @ul class: 'list', =>
+            for sourcePath, deprecations of deprecationsByFile
+              @li class: 'list-item source-file', =>
+                @a class: 'source-url', href: path.join(deprecations[0].packagePath, sourcePath), sourcePath
+                @ul class: 'list', =>
+                  for deprecation in deprecations
+                    @li class: 'list-item deprecation-detail', =>
+                      @span class: 'text-warning icon icon-alert'
+                      @div class: 'list-item deprecation-message', =>
+                        @raw marked(deprecation.message)
+
+                      @div class: 'btn-toolbar', =>
+                        if url = self.createSelectorIssueUrl(packageName, deprecation, sourcePath)
+                          @a class: 'issue-url', href: url, "Create Issue on #{packageName} repo"


Update time and scroll to bottom on message.

--- client/chanslate.coffee
+++ client/chanslate.coffee
@@ -30,24 +30,21 @@
 Meteor.subscribe('chanslateMessages')
 Session.set('userName', haiku())

-# Behavior helpers
-timer = null
-autoScroll = true
-scrollToBottom = ->
-    clearTimeout(timer)
-    if autoScroll
-        timer = setTimeout(
-            -> $('#chanslate-content').animate({scrollTop: 999999}, 1000),
-            90
-        )
-
 # Global helpers
 Handlebars.registerHelper("unescape",  (html) ->
     e = document.createElement('div')
     e.innerHTML = html
     if e.childNodes.length == 0 then '' else e.childNodes[0].nodeValue
 )
-Handlebars.registerHelper("ago", (time) -> moment(time).fromNow())
+
+# Hack to get time to update every 5 seconds.
+updateTime = -> Session.set('now', new Date())
+Meteor.setInterval(updateTime, 5000)
+
+Handlebars.registerHelper("ago", (time) ->
+    now = Session.get('now')
+    moment(time).from(now)
+)

 ###
 # Set a color based on handle
@@ -62,13 +59,15 @@

 Handlebars.registerHelper('colorize', -> colorHandle(this["userName"]))

+scrollToBottom =  ->
+    $('html, body').animate({ scrollTop: 99999999 }, "slow")
+
 # Template helpers
 Template.showMessages.helpers({
     messages: ->
-        ChanslateMessages.find({}, {
-            sort:
-                at: 1
-        })
+        cursor = ChanslateMessages.find({}, {sort: { at: 1 }})
+        cursor.observe({ added: scrollToBottom })
+        cursor
 })

 Template.postMessage.events(


Coffeescript is crazy!!

Coffeescript is crazy!!

--- lib/pg-hoff-results-view.coffee
+++ lib/pg-hoff-results-view.coffee
@@ -16,26 +16,12 @@
     resultsets: []

     canTypeBeSorted: (typeCode) ->
-        t = Type[typeCode]
-        if t
-            return t.compare?
-
-        return false
+        return Type[typeCode]?

     compare: (typeCode, left, right, asc) ->
-        type = Type[typeCode]
+        return Type[typeCode]?.compare(left, right) * if asc then 1 else -1 ? 0

-        if type.compare?
-            val = type.compare(left, right)
-
-            if !asc
-                val = val * -1
-
-            return val
-
-        return 0
-
-    sort: (resultset, columnIndex, asc, resultsView) ->
+    sort: (resultset, columnIndex, asc) ->
         if resultset.columns[columnIndex].ascending?
             resultset.columns[columnIndex].ascending = !resultset.columns[columnIndex].ascending
         else
@@ -47,27 +33,24 @@
             console.error('This type is not sortable', typeCode)
             return

-        compare = resultsView.compare
-
-        resultset.rows.sort (left, right) ->
-            return compare(typeCode, left[columnIndex], right[columnIndex], ascending)
+        resultset.rows.sort (left, right) =>
+            return @compare(typeCode, left[columnIndex], right[columnIndex], ascending)

     createTh: (text, resultsetIndex, columnIndex) ->
-        resultsView = @
         th = document.createElement('th')
         th.textContent = text
         th.setAttribute('column_index', columnIndex)
         th.setAttribute('resultset_index', resultsetIndex)
-        if resultsView.canTypeBeSorted(resultsView.resultsets[resultsetIndex].columns[columnIndex].type_code)
+        if @canTypeBeSorted(@resultsets[resultsetIndex].columns[columnIndex].type_code)
             th.classList.add('sortable')
-            if resultsView.resultsets[resultsetIndex].columns[columnIndex].ascending == true
+            if @resultsets[resultsetIndex].columns[columnIndex].ascending == true
                 th.textContent = th.textContent + ' +'
-            else if resultsView.resultsets[resultsetIndex].columns[columnIndex].ascending == false
+            else if @resultsets[resultsetIndex].columns[columnIndex].ascending == false
                 th.textContent = th.textContent + ' -'

-        th.onclick = ->
-            resultsView.sort(resultsView.resultsets[this.getAttribute('resultset_index')], this.getAttribute('column_index'), true, resultsView)
-            resultsView.update(resultsView.resultsets)
+        th.onclick = =>
+            @sort(@resultsets[this.getAttribute('resultset_index')], this.getAttribute('column_index'), true)
+            @update(@resultsets)
         return th

     createTable: (x, resultsetIndex) ->
@@ -97,19 +80,15 @@
         # Header columns
         if x.columns?
             col_tr = table.appendChild(document.createElement('tr'))
-            i = 0
-            for c in x.columns
+            for c, i in x.columns
                 col_tr.appendChild(@createTh(c.name, resultsetIndex, i))
-                i = i + 1

         # Rows
         if x.rows?
             for r in x.rows
                 row_tr = table.appendChild(document.createElement('tr'))
-                i = 0
-                for c in r
+                for c, i in r
                     row_tr.appendChild(@createTd(c, x.columns[i].type_code))
-                    i = i + 1

         return container

@@ -171,10 +150,8 @@
         clear = toolbar.appendChild(document.createElement('div'))
         clear.classList.add('clear')

-        i = 0
-        for resultset in resultsets
+        for resultset, i in resultsets
             @element.appendChild(@createTable(resultset, i))
-            i++

     destroy: ->
         @element.remove()


注释同步项目中的添加按钮

注释同步项目中的添加按钮

--- lib/project/sync-project.coffee
+++ lib/project/sync-project.coffee
@@ -12,9 +12,9 @@
           @div class: 'sync-item inline-block text-center', =>
             @img class: 'pic', src: desc.iconPath
             @h3 '测试项目',class: 'project-name'
-          @div class: 'sync-item inline-block add text-center', =>
-            @div class: 'add-icon icon icon-plus'
-            @h3 '新建同步项目', class: 'project-name'
+          # @div class: 'sync-item inline-block add text-center', =>
+          #   @div class: 'add-icon icon icon-plus'
+          #   @h3 '新建同步项目', class: 'project-name'

   getElement: ->


Remove unused variable

Remove unused variable

--- resources/assets/lib/play-detail.coffee
+++ resources/assets/lib/play-detail.coffee
@@ -26,12 +26,6 @@
 export PlayDetail = (props) ->
   score = props.score
   pp = score.best?.pp ? score.pp
-
-  modsText =
-    if score.mods.length
-      " +#{(mod.shortName for mod in score.mods).join(',')} "
-    else
-      ' '

   classMods = if props.activated then ['menu-active'] else ['highlightable']


Subscribe to atom.workspaceView.eachEditorView

--- lib/selection-count-view.coffee
+++ lib/selection-count-view.coffee
@@ -6,7 +6,7 @@
     @div class: "selection-count inline-block"

   initialize: (@statusBar) ->
-    atom.workspaceView.eachEditorView (editor) =>
+    @subscribe atom.workspaceView.eachEditorView (editor) =>
       @subscribe editor, "selection:changed", @updateCount
     @subscribe atom.workspaceView, "pane-container:active-pane-item-changed", @updateCount


remove non-working metrics from graphite

--- libraries/metrics/memory.coffee
+++ libraries/metrics/memory.coffee
@@ -70,14 +70,14 @@
 		Metrics.gauge("memory.heaptotal", mem.heapTotal)
 		Metrics.gauge("memory.heapused", mem.heapUsed)
 		Metrics.gauge("memory.gc-interval", gcInterval)
-		Metrics.gauge("memory.cpu-time-bucket", CpuTimeBucket)
+		#Metrics.gauge("memory.cpu-time-bucket", CpuTimeBucket)

 		if global.gc? && readyToGc()
 			gcTime = (executeAndTime	global.gc).toFixed(2)
 			memAfterGc = inMegaBytes process.memoryUsage()
 			deltaMem = updateMemoryStats(memBeforeGc, memAfterGc)
 			logger.log {gcTime, memBeforeGc, memAfterGc, deltaMem, gcInterval, CpuTimeBucket}, "global.gc() forced"
-			Metrics.timing("memory.gc-time", gcTime)
+			#Metrics.timing("memory.gc-time", gcTime)
 			Metrics.gauge("memory.gc-rss-freed", -deltaMem.rss)
 			Metrics.gauge("memory.gc-heaptotal-freed", -deltaMem.heapTotal)
 			Metrics.gauge("memory.gc-heapused-freed",  -deltaMem.heapUsed)


Use single constructor

Use single constructor

--- app/assets/javascripts/controllers/calendar/index.js.coffee
+++ app/assets/javascripts/controllers/calendar/index.js.coffee
@@ -5,9 +5,7 @@
 class Calendar.IndexController extends Global.GenericController
   constructor: ($location, $http, notifier) ->
     this.notifier = notifier
-    this.path = $location.$$path + '.json'
-    this.http = $http
-    this.request()
+    super($location, $http)

   _setData: (response) =>
     super


Check for existence of workWeek before checking its estimatedHours

--- app/assets/javascripts/views/staffplans/user_view.js.coffee
+++ app/assets/javascripts/views/staffplans/user_view.js.coffee
@@ -161,7 +161,7 @@
       total = if workWeek? then workWeek[if date.weekHasPassed then 'actual' else 'estimated'] else 0
       if total == 0 && date.weekHasPassed
         noActualsForWeek = true
-        total = workWeek['estimated'] || 0
+        total = (if workWeek? then workWeek['estimated'] else 0)  || 0

       li
         .height(total * ratio)


Fixed the employee count validation to show the errors and fixed limit

only the latest year has limit of 2, it is otherwise 1

--- app/assets/javascripts/frontend/form-validation.js.coffee
+++ app/assets/javascripts/frontend/form-validation.js.coffee
@@ -177,7 +177,7 @@
         subq = $(subquestion)
         if not subq.val() and question.hasClass("question-required")
           @log_this(question, "validateEmployeeMin", "This field is required")
-          @appendMessage(subq.closest("label"), "This field is required")
+          @appendMessage(subq.closest(".span-financial"), "This field is required")
           @addErrorClass(question)
           continue
         else if not subq.val()
@@ -185,12 +185,20 @@

         if not subq.val().toString().match(@numberRegex)
           @log_this(question, "validateEmployeeMin", "Not a valid number")
-          @appendMessage(subq.closest("label"), "Not a valid number")
+          @appendMessage(subq.closest(".span-financial"), "Not a valid number")
           @addErrorClass(question)
         else
-          if parseInt(subq.val()) < 2
-            @log_this(question, "validateEmployeeMin", "Minimum of 2 employees")
-            @appendMessage(subq.closest("label"), "Minimum of 2 employees")
+          subq_list = subq.closest(".row").find(".span-financial")
+          subq_index = subq_list.index(subq.closest(".span-financial"))
+
+          if subq_index == subq_list.size() - 1
+            employee_limit = 2
+          else
+            employee_limit = 1
+
+          if parseInt(subq.val()) < employee_limit
+            @log_this(question, "validateEmployeeMin", "Minimum of #{employee_limit} employees")
+            @appendMessage(subq.closest(".span-financial"), "Minimum of #{employee_limit} employees")
             @addErrorClass(question)

   validateTotalOverseas: (question) ->


Fix alignment

Fix alignment

--- src/modules/link-tooltip.coffee
+++ src/modules/link-tooltip.coffee
@@ -1,7 +1,7 @@
-Quill    = require('../quill')
-Tooltip  = require('./tooltip')
-_        = Quill.require('lodash')
-dom      = Quill.require('dom')
+Quill   = require('../quill')
+Tooltip = require('./tooltip')
+_       = Quill.require('lodash')
+dom     = Quill.require('dom')

 class LinkTooltip extends Tooltip
   @DEFAULTS:


Constrain LinkTooltip#_findAnchor to not search past @quill.root

--- src/modules/link-tooltip.coffee
+++ src/modules/link-tooltip.coffee
@@ -91,7 +91,7 @@
   _findAnchor: (range) ->
     [leaf, offset] = @quill.editor.doc.findLeafAt(range.start, true)
     node = leaf.node if leaf?
-    while node?
+    while node? and node != @quill.root
       return node if node.tagName == 'A'
       node = node.parentNode
     return null


prefer first milestone with due date

prefer first milestone with due date

--- src/modules/milestones.coffee
+++ src/modules/milestones.coffee
@@ -1,4 +1,5 @@
 #!/usr/bin/env coffee
+{ _ } = require 'lodash'
 request = require './request'

 module.exports =
@@ -12,8 +13,10 @@
             return cb data.message if data.message
             # Empty warning?
             return cb null, 'No open milestones for repo' unless data.length
-            # The first milestone should be ending soonest.
-            m = data[0]
+            # Filter milestones without due date.
+            m = _.rest data, { 'due_on' : null }
+            # The first milestone should be ending soonest. Prefer milestones with due dates.
+            m = if m[0] then m[0] else data[0]
             # Empty milestone?
             return cb null, 'No issues for milestone' if m.open_issues + m.closed_issues is 0


Unwrap functions earlier to treat them properly in getCommands

--- src/ReplrClient.coffee
+++ src/ReplrClient.coffee
@@ -104,7 +104,10 @@

   getCommands: ()->
-    exported = @repl.context.exported
+    exported = {}
+    for key, value of @repl.context.exported
+      # Use the unbound version of the method for unwrapping documentation if available
+      exported[key] = if typeof value.unbound == 'function' then value.unbound else value
     commands = (key for key in Object.keys(exported) when typeof exported[key] == 'function')

     signatureAsString = (name, func)-> "#{key}(#{doc.docArgsAsArray(func).join(', ')})"
@@ -119,8 +122,6 @@
     descriptions = []
     for key in commands
       func = exported[key]
-      # Use the unbound version of the method for unwrapping documentation if available
-      func = if typeof func.unbound == 'function' then func.unbound else func
       signature = terminal.rpad signatureAsString(key, func), indentBy
       described = ''
       terminal.printWrapped doc.docAsString(func), 80, indentBy, (out)->


Fixing competitions/by-membership API

Fixing competitions/by-membership API

--- api.coffee
+++ api.coffee
@@ -24,6 +24,7 @@
       val

   listUsers: (userId) ->
+    # TODO: set .lean() on query for performance
     console.log "[API] list users (userId=#{userId})"
     if userId?
       query = _id: ObjectID(userId)
@@ -56,13 +57,16 @@
       .exec()
     )
     .then((memberships) ->
-      Q(
-        Competition.find().where('_id')
-        .or(memberships.map (membership) ->
-          { _id: membership.competition_id }
-        )
+      membershipIds = memberships.map((membership) ->
+        _id: membership.competition_id
       )
+      anyCompetition = { $or: membershipIds }
+      Q(Competition.find(anyCompetition).exec())
     )
+
+  listCompetitionMemberships: ->
+    console.log "[API] list competition memberships"
+    Q(CompetitionMembership.find().exec())

   joinCompetition: (userId, competitionId) ->
     console.log "[API] join competition (userId=#{userId}, competitionId=#{competitionId})"


Force deletion and stop computations of templates when closing room

--- packages/rocketchat-ui/lib/RoomManager.coffee
+++ packages/rocketchat-ui/lib/RoomManager.coffee
@@ -69,7 +69,9 @@
 			openedRooms[typeName].ready = false
 			openedRooms[typeName].active = false
 			delete openedRooms[typeName].timeout
+			Blaze.remove openedRooms[typeName].template
 			delete openedRooms[typeName].dom
+			delete openedRooms[typeName].template

 			if openedRooms[typeName].rid?
 				RoomHistoryManager.clear openedRooms[typeName].rid
@@ -163,7 +165,11 @@
 		if not room.dom? and rid?
 			room.dom = document.createElement 'div'
 			room.dom.classList.add 'room-container'
-			Blaze.renderWithData Template.room, { _id: rid }, room.dom
+			contentAsFunc = (content) ->
+				return -> content
+
+			room.template = Blaze._TemplateWith { _id: rid }, contentAsFunc(Template.room)
+			Blaze.render room.template, room.dom #, nextNode, parentView

 		return room.dom


Remove multi process searching garbage

Remove multi process searching garbage

--- src/multi-process-search.coffee
+++ src/multi-process-search.coffee
@@ -1,195 +0,0 @@
-_ = require 'underscore'
-PathSearcher = require './path-searcher'
-PathScanner = require './path-scanner'
-path = require "path"
-split = require "split"
-
-###
-Multiprocess.
-
-This is an experiment. The hope was that it would be faster than the single
-process search. However, it tuned out to be way more complicated, unreliable,
-AND slower. Not good.
-
-NOTES:
-
-  * This uses stdin/out to communicate between processes. I tried
-    process.send, but it often the child process would not receive the messages!
-
-  * Knowing when to kill the processes is hard. There is a lot of complication
-    in the main process to keep track of the state of each child process so we
-    know when to halt.
-
-  * Sometimes on large files (right now, minified less.js) on large searches
-    (16k files) seem to make it not complete.
-###
-
-
-makeTask = (fn) ->
-  "(#{fn.toString()})();"
-
-fork = (task, env) ->
-  child_process = require 'child_process'
-  args = [task, '--harmony_collections']
-  # silent: true is important so we can access stdin/out
-  child_process.fork '--eval', args, {env, cwd: __dirname, silent: true}
-
-kill = (childProcess) ->
-  childProcess.removeAllListeners()
-  childProcess.kill()
-  null
-
-# Scan and emit jsonified list of paths on stdout. Each line is a list.
-# \0\n inidicates that it's done.
-scan = ->
-  PathScanner = require './path-scanner'
-
-  PATHS_TO_SEARCH = 50
-  STOP_CHAR = String.fromCharCode(0)
-
-  emitPaths = (paths) ->
-    process.stdout.write(JSON.stringify(paths)+'\n')
-  emitEnd = ->
-    process.stdout.write(STOP_CHAR+'\n')
-
-  options =
-    pathToScan: process.env.pathToScan
-    showHidden: process.env.showHidden == 'true'
-    excludeVcsIgnores: process.env.excludeVcsIgnores == 'true'
-
-  scanner = new PathScanner(options.pathToScan, options)
-
-  paths = []
-  scanner.on 'path-found', (path) ->
-    paths.push(path)
-    if paths.length == PATHS_TO_SEARCH
-      emitPaths(paths)
-      paths = []
-
-  scanner.on 'finished-scanning', ->
-    emitPaths(paths) if paths.length
-    emitEnd()
-
-  scanner.scan()
-
-# Search and emit jsonified results objects. Each line is a object for a
-# single path's results.
-# \1\n - indicates start of search
-# {...}\n - indicates results
-# \4\n - indicates end of search
-#
-# This thing never knows when it is completely done. You can just keep sending
-# it lists of paths to search.
-search = ->
-  PathSearcher = require './path-searcher'
-
-  searches = 0
-
-  START_CHAR = String.fromCharCode(1)
-  END_CHAR = String.fromCharCode(4)
-  STOP_CHAR = String.fromCharCode(0)
-
-  emitStart = ->
-    process.stdout.write(START_CHAR+'\n')
-  emitResults = (results) ->
-    if results
-      results = (JSON.stringify(res) for res in results).join('\n')
-      process.stdout.write(results+'\n')
-    emitEnd()
-  emitEnd = ->
-    process.stdout.write(END_CHAR+'\n')
-
-  options = process.env
-  searcher = new PathSearcher()
-  regex = new RegExp(options.search, 'gi')
-
-  soFar = ''
-  readLines = (stdin) ->
-    buf = stdin.read()
-    return null unless buf
-
-    # Lifted from https://github.com/dominictarr/split/blob/master/index.js
-    pieces = (soFar + buf).split('\n')
-    soFar = pieces.pop()
-    pieces
-
-  process.stdin.on 'readable', ->
-    lines = readLines(process.stdin)
-    return unless lines
-
-    for line in lines
-      emitStart()
-      if !line or line[0] == STOP_CHAR
-        emitEnd()
-      else
-        paths = JSON.parse(line)
-        searcher.searchPaths regex, paths, (results) ->
-          emitResults(results)
-
-searchMain = (options) ->
-  options.pathToScan = path.resolve(options.pathToScan)
-  env = _.extend({}, process.env, options)
-
-  START_CHAR = String.fromCharCode(1)
-  END_CHAR = String.fromCharCode(4)
-  STOP_CHAR = String.fromCharCode(0)
-
-  scanTask = makeTask(scan)
-  searchTask = makeTask(search)
-
-  console.time 'Multi Process Search'
-
-  searches = 0
-  finished = false
-  scanFinished = false
-
-  resultCount = 0
-  pathCount = 0
-
-  scanProcess = fork(scanTask, env)
-  searchProcess = fork(searchTask, env)
-
-  searchProcess.stdin.setEncoding = 'utf-8'
-  scanProcess.stdout.pipe(split()).on 'data', (data) ->
-    if data[0] == STOP_CHAR
-      scanFinished = true
-      maybeEnd()
-    else
-      searches++
-      searchProcess.stdin.write(data+'\n')
-
-  searchProcess.stderr.pipe(process.stderr)
-
-  searchProcess.stdout.pipe(split()).on 'data', (line) ->
-    if line[0] == START_CHAR
-      console.log "search start" if options.verbose
-    else if line[0] == END_CHAR
-      searches--
-      console.log "search end #{searches}" if options.verbose
-    else if line and line.length
-      results = JSON.parse(line)
-      if results
-        pathCount++
-        resultCount += results.matches.length
-        console.log "#{results.matches.length} matches in #{results.path}" if options.verbose
-
-    maybeEnd()
-
-  scanProcess.on 'message', ({event, arg}) ->
-    console.log event, arg
-  searchProcess.on 'message', ({event, arg}) ->
-    console.log event, arg
-
-  maybeEnd = ->
-    return if finished
-    if scanFinished and searches == 0
-      kill(scanProcess)
-      kill(searchProcess)
-      end()
-
-  end = ->
-    finished = true
-    console.timeEnd 'Multi Process Search'
-    console.log "#{resultCount} matches in #{pathCount} files"
-
-module.exports = {searchMain}


Slightly rewrite animation loop starting code

--- framer/Framer.coffee
+++ framer/Framer.coffee
@@ -68,11 +68,10 @@
 Framer.Extras.MobileScrollFix.enable() if Utils.isMobile()
 Framer.Extras.TouchEmulator.enable() if not Utils.isTouch()
 Framer.Extras.ErrorDisplay.enable() if not Utils.isFramerStudio()
+Framer.Extras.Preloader.enable() if not Utils.isFramerStudio()
 Framer.Extras.Hints.enable()

-Utils.domComplete ->
-	if not Utils.isFramerStudio()
-		Framer.Extras.Preloader.enable()
-		Framer.Preloader.once("end", Framer.Loop.start)
-	else
-		Framer.Loop.start()
+if Framer.Preloader?
+	Framer.Preloader.once("end", Framer.Loop.start)
+else
+	Utils.domComplete(Framer.Loop.start)


another bump git log

another bump
git log

--- app.coffee
+++ app.coffee
@@ -96,7 +96,7 @@

 app.get '/status', (req, res)->
 	if appIsOk
-		res.send('filestore sharelatex up - v4')
+		res.send('filestore sharelatex up - v5')
 	else
 		logger.log "app is not ok - shutting down"
 		res.send("server is being shut down", 500)


[fix] remove reference to removed report.index route

--- app.coffee
+++ app.coffee
@@ -51,10 +51,8 @@
 app.get('/', routes.index)
 app.get('/users', user.list)
 app.get('/map', map.index)
-app.get('/report', report.index)
 app.get('/navigate/nav', navigate.nav)
 app.get('/navigate/search', navigate.searchmap)
-#app.get('/report', report.index)
 app.get('/report/getall', report.getall)
 app.post('/report/submit', report.submit)
 app.get('/intersections/all', intersections.all)