Remove percentage loader coffee

Remove percentage loader coffee

--- app/assets/javascripts/percentageloader.coffee
+++ app/assets/javascripts/percentageloader.coffee
@@ -1,278 +0,0 @@
-# jquery.percentageloader.js
-
-# Copyright (c) 2012, Better2Web
-# All rights reserved.
-
-# This jQuery plugin is licensed under the Simplified BSD License. Please
-# see the file license.txt that was included with the plugin bundle.
-
-'use strict'
-
-(($) ->
-
-
-  # Our spiral gradient data
-  gradient = new Image()
-  gradient.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAIAAABMXPacAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MDM4RjdFNzQ5MzAyMTFFMUFFQTdENUVDNDUwOEI2RUYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MDM4RjdFNzU5MzAyMTFFMUFFQTdENUVDNDUwOEI2RUYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowMzhGN0U3MjkzMDIxMUUxQUVBN0Q1RUM0NTA4QjZFRiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowMzhGN0U3MzkzMDIxMUUxQUVBN0Q1RUM0NTA4QjZFRiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv4BSzoAAA+OSURBVHja1F1bkutK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"
-
-  ### Percentage loader
-    @param	params	Specify options in {}. May be on of width, height, progress or value.
-    @example $("#myloader-container).percentageLoader({
-      width : 256,  // width in pixels
-      height : 256, // height in pixels
-      progress: 0,  // initialise progress bar position, within the range [0..1]
-      value: '0kb'  // initialise text label to this value
-  	});
-  ###
-  $.fn.percentageLoader = (params) ->
-
-    # Specify default settings
-    settings =
-      width: 256
-      height: 256
-      progress: 0
-      value: '0kb'
-
-    # Override default settings with provided params, if any
-    if (params != `undefined`) then $.extend(settings, params) else params = settings
-
-    outerDiv = document.createElement('div')
-    outerDiv.style.width = settings.width + 'px'
-    outerDiv.style.height = settings.height + 'px'
-    outerDiv.className = 'percentage-loader'
-
-    $(this).append(outerDiv)
-
-    # Create our canvas object
-    canvas = document.createElement('canvas')
-    canvas.setAttribute('width', settings.width)
-    canvas.setAttribute('height', settings.height)
-    outerDiv.appendChild(canvas)
-
-    ###
-      Create div elements we'll use for text. Drawing text is
-      possible with canvas but it is tricky working with custom
-      fonts as it is hard to guarantee when they become available
-      with differences between browsers. DOM is a safer bet here
-    ###
-    percentageText = document.createElement('div')
-    percentageText.className = 'percentage-text'
-
-    valueText = document.createElement('div')
-    valueText.className = 'value-text'
-
-    # Add the new dom elements to the containing div
-    outerDiv.appendChild(percentageText)
-    outerDiv.appendChild(valueText)
-
-    # Get a reference to the context of our canvas object
-    ctx = canvas.getContext("2d")
-
-
-    # Set various initial values
-
-    # Centre point
-    cX = (canvas.width / 2) - 1
-    cY = (canvas.height / 2) - 1
-
-    # Create our linear gradient for the outer ring
-    lingrad = ctx.createLinearGradient(cX, 0, cX, canvas.height)
-    lingrad.addColorStop(0, '#d6eeff')
-    lingrad.addColorStop(1, '#b6d8f0')
-
-    # Create inner gradient for the outer ring
-    innerGrad = ctx.createLinearGradient(cX, cX * 0.133333, cX, canvas.height - cX * 0.133333)
-    innerGrad.addColorStop(0, '#f9fcfe')
-    innerGrad.addColorStop(1, '#d9ebf7')
-
-    # Tube gradient (background, not the spiral gradient)
-    tubeGrad = ctx.createLinearGradient(cX, 0, cX, canvas.height)
-    tubeGrad.addColorStop(0, '#c1dff4')
-    tubeGrad.addColorStop(1, '#aacee6')
-
-    # The inner circle is 2/3rds the size of the outer one
-    innerRadius = cX * 0.6666
-    # Outer radius is the same as the width / 2, same as the centre x
-    # (but we leave a little room so the borders aren't truncated)
-    radius = cX - 2
-
-    # Calculate the radii of the inner tube
-    innerBarRadius = innerRadius + (cX * 0.06);
-    outerBarRadius = radius - (cX * 0.06);
-
-    # Bottom left angle
-    startAngle = 2.1707963267949;
-    # Bottom right angle
-    endAngle = 0.9707963267949 + (Math.PI * 2.0);
-
-    # Nicer to pass counterClockwise / clockwise into canvas functions than true / false
-    counterClockwise = false;
-
-    # Borders should be 1px
-    ctx.lineWidth = 1;
-
-    ###
-      Little helper method for transforming points on a given
-      angle and distance for code clarity
-    ###
-    applyAngle = (point, angle, distance) ->
-      x : point.x + (Math.cos(angle) * distance)
-      y : point.y + (Math.sin(angle) * distance)
-
-
-    ###
-      render the widget in its entirety.
-    ###
-    drawLoader = ->
-      # Clear c|anvas entirely
-      ctx.clearRect(0, 0, canvas.width, canvas.height)
-
-      ### IMAGERY ###
-
-      # draw outer circle
-      ctx.fillStyle = lingrad
-      ctx.beginPath()
-      ctx.strokeStyle = '#b2d5ed'
-      ctx.arc(cX, cY, radius, 0, Math.PI * 2, counterClockwise)
-      ctx.fill()
-      ctx.stroke()
-
-      # draw inner circle
-      ctx.fillStyle = innerGrad
-      ctx.beginPath()
-      ctx.arc(cX, cY, innerRadius, 0, Math.PI * 2, counterClockwise)
-      ctx.fill()
-      ctx.strokeStyle = '#b2d5edaa'
-      ctx.stroke()
-
-      ctx.beginPath()
-
-      ###
-        Helper function - adds a path (without calls to beginPath or closePath)
-        to the context which describes the inner tube. We use this for drawing
-        the background of the inner tube (which is always at 100%) and the
-        progress meter itself, which may vary from 0-100%
-      ###
-      makeInnerTubePath = (startAngle, endAngle) ->
-        centrePoint =
-          x : cX
-          y : cY
-
-        startPoint = applyAngle(centrePoint, startAngle, innerBarRadius)
-
-        ctx.moveTo(startPoint.x, startPoint.y)
-
-        point1 = applyAngle(centrePoint, endAngle, innerBarRadius)
-        point2 = applyAngle(centrePoint, endAngle, outerBarRadius)
-
-        controlAngle = endAngle + (3.142 / 2.0)
-        # Cap length - a fifth of the canvas size minus 4 pixels
-        capLength = (cX * 0.20) - 4
-
-        c1 = applyAngle(point1, controlAngle, capLength)
-        c2 = applyAngle(point2, controlAngle, capLength)
-
-        ctx.arc(cX, cY, innerBarRadius, startAngle, endAngle, false)
-        ctx.bezierCurveTo(c1.x, c1.y, c2.x, c2.y, point2.x, point2.y)
-        ctx.arc(cX, cY, outerBarRadius, endAngle, startAngle, true)
-
-        point1 = applyAngle(centrePoint, startAngle, innerBarRadius)
-        point2 = applyAngle(centrePoint, startAngle, outerBarRadius)
-
-        controlAngle = startAngle - (3.142 / 2.0)
-
-        c1 = applyAngle(point2, controlAngle, capLength)
-        c2 = applyAngle(point1, controlAngle, capLength)
-
-        ctx.bezierCurveTo(c1.x, c1.y, c2.x, c2.y, point1.x, point1.y)
-
-      # Background tube
-      ctx.beginPath()
-      ctx.strokeStyle = '#bcd4e5'
-      makeInnerTubePath(startAngle, endAngle)
-
-      ctx.fillStyle = tubeGrad
-      ctx.fill()
-      ctx.stroke()
-
-      # Calculate angles for the the progress metre
-      completeAngle = startAngle + (progress * (endAngle - startAngle))
-
-      ctx.beginPath()
-      makeInnerTubePath(startAngle, completeAngle)
-
-      # We're going to apply a clip so save the current state
-      ctx.save()
-      # Clip so we can apply the image gradient
-      ctx.clip()
-
-      # Draw the spiral gradient over the clipped area
-      ctx.drawImage(gradient, 0, 0, canvas.width, canvas.height)
-
-      # Undo the clip
-      ctx.restore()
-
-      # Draw the outline of the path
-      ctx.beginPath()
-      makeInnerTubePath(startAngle, completeAngle)
-      ctx.stroke()
-
-      ### TEXT ###
-      # Calculate the size of the font based on the canvas size
-      fontSize = cX / 2
-
-      percentageText.style.top = ((settings.height / 2) - (fontSize / 2)).toString() + 'px'
-      percentageText.style.fontSize = fontSize.toString() + 'px'
-
-      # Calculate the text for the given percentage
-      string = (progress * 100.0).toFixed(0) + '%'
-
-      percentageText.innerHTML = string
-
-      # Calculate font and placement of small 'value' text
-      smallSize = cX / 5.5
-      valueText.style.fontSize = smallSize.toString() + 'px'
-      valueText.style.height = smallSize.toString() + 'px'
-
-      ###
-        Ugly vertical align calculations - fit into bottom ring.
-        The bottom ring occupes 1/6 of the diameter of the circle
-      ###
-      heightRemaining = (settings.height * 0.16666666) - smallSize
-      valueText.style.top = ((settings.height * 0.8333333) + (heightRemaining / 4)).toString() + 'px'
-
-    ###
-      Check the progress value and ensure it is within the correct bounds [0..1]
-    ###
-    clipValue = ->
-      if (progress < 0) then progress = 0
-      if (progress > 1.0) then progress = 1.0
-
-    ###
-      Sets the current progress level of the loader
-      @param value the progress value, from 0 to 1. Values outside this range will be clipped
-    ###
-    setProgress = (value) ->
-      # Clip values to the range [0..1]
-      progress = value
-      clipValue()
-      drawLoader()
-
-    @setProgress = setProgress
-
-    setValue = (val) ->
-      valueText.innerHTML = val
-
-    @setValue = setValue
-    @setValue(settings.value)
-
-    progress = settings.progress
-
-    clipValue()
-
-    # Do an initial draw
-    drawLoader()
-
-    return @
-
-) jQuery


Grunt: Watch CoffeeScript files, not compiled JS files

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -13,8 +13,8 @@
         ]

     watch:
-      lib:
-        files: ['lib/**/*.js']
+      src:
+        files: ['src/**/*.coffee']
         tasks: ['coffee', 'mocha']

       test:


Minimum healthy weight change is now dynamic based on current weight, closes #519

--- app/assets/javascripts/tools/bmi_ahi.js.coffee
+++ app/assets/javascripts/tools/bmi_ahi.js.coffee
@@ -19,7 +19,7 @@

   $(document).on 'click', "[data-object~='calculate-minimum-weight']", () ->
-    $("#desired-weight").val minimum_healthy_weight(calculate_height())
+    $("#desired-weight").val minimum_healthy_weight(calculate_height(), calculate_bmi())
     calculate_predicted_ahi_change()
     scatter_pt = calculate_scatter_position()
     output_AHI_change(scatter_pt[0], scatter_pt[1])
@@ -51,9 +51,13 @@
   # If all data is entered, show the BMI graph, the AHI graph,
   # and autocomplete necessary weight for healthy BMI (if applicable)
   if feet_updated and inches_updated and current_weight_updated
-    $('#bmi-graph svg.chart').html("")
-    draw_bmi_graph()
-    $("#bmi-ahi-results-container").removeClass "hidden"
+    output_BMI()
+  return get_bmi(height,weight)
+
+output_BMI = () ->
+  $('#bmi-graph svg.chart').html("")
+  draw_bmi_graph()
+  $("#bmi-ahi-results-container").removeClass "hidden"

 weight_vs_ahi = (old_weight, new_weight) ->
   weight_change = ((new_weight-old_weight)/old_weight) * 100
@@ -62,8 +66,12 @@
 get_bmi = (height, weight) ->
   Math.round((weight / (height ** 2)) * 703)

-minimum_healthy_weight = (height) ->
-  Math.round(25 * (height ** 2) / 703)
+minimum_healthy_weight = (height, bmi) ->
+  desired_bmi = calculate_desired_BMI(bmi)
+  if desired_bmi==bmi
+    return parseFloat($("#my-weight").val())
+  else
+    Math.round(desired_bmi * (height ** 2) / 703)

 calculate_scatter_position = () ->
   old_w = parseFloat($("#my-weight").val())
@@ -76,6 +84,25 @@
   $("#ahi").removeClass "hidden"
   draw_ahi_graph2(x)

+calculate_desired_BMI = (bmi) ->
+  if bmi < 18.5
+    return 18.6
+  else if bmi < 25
+    return bmi
+  else if bmi < 30
+    return 24.9
+  else
+    return 29.9
+
+calculate_BMI_category = (bmi) ->
+  if bmi < 18.5
+    return "Underweight"
+  else if bmi < 25
+    return "Normal weight"
+  else if bmi < 30
+    return "Overweight"
+  else
+    return "Obese"

 draw_bmi_graph = () ->
   data = [
@@ -134,7 +161,6 @@
     .attr("y2", h+1)
     .attr("stroke-width", 2)
     .attr("stroke", "black")
-

 draw_ahi_graph = () ->


Clean trailing whitespace from Gruntfile.coffee

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -3,7 +3,7 @@
     clean:
       dist: ['dist/']
       coffee: ['dist/*.coffee']
-
+
     coffee:
       compile:
         expand: true
@@ -14,17 +14,17 @@

     uglify:
       dist:
-        files:
+        files:
           'dist/Bacon.min.js': 'dist/Bacon.min.js'

-    copy:
-      dist:
+    copy:
+      dist:
         expand:true
         files:[
           'dist/Bacon.coffee': 'src/Bacon.coffee'
         ]
-
-
+
+
   grunt.loadNpmTasks 'grunt-contrib-coffee'
   grunt.loadNpmTasks 'grunt-contrib-clean'
   grunt.loadNpmTasks 'grunt-contrib-copy'
@@ -37,4 +37,4 @@
     fs = require 'fs'
     file = fs.readFileSync('dist/Bacon.coffee', 'utf8')
     replacedData = file.replace(/assert.*/g, '')
-    fs.writeFileSync('dist/Bacon.noAssert.coffee', replacedData);+    fs.writeFileSync('dist/Bacon.noAssert.coffee', replacedData);


Clearer separation between browser and Node.js builds

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -157,7 +157,7 @@
   @registerTask 'build', 'Build', (target = 'all') =>
     @task.run 'yaml'
     @task.run 'coffee'
-    if target != 'nodejs'
+    if target in ['all', 'browser']
       @task.run 'webpack'
       @task.run 'examples:bundle'
       @task.run 'copy:ui'
@@ -166,16 +166,14 @@
     @task.run 'coffeelint'
     @task.run 'yamllint'
     @task.run "build:#{target}"
-    @task.run 'mochaTest'
-    if target != 'nodejs'
+    if target in ['all', 'nodejs']
+      @task.run 'mochaTest'
+    if target in ['all', 'browser']
       @task.run 'downloadfile'
-      @task.run 'connect'
       @task.run 'karma'

   @registerTask 'default', ['test']

-  @registerTask 'uidev', ['connect:server:keepalive']
-
   @registerTask 'dev', 'Developing', (target = 'all') =>
     @task.run 'test'
     @task.run 'watch'


Made jshint watch much faster

Made jshint watch much faster

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -34,6 +34,7 @@
   grunt.registerTask 'test:firefox', 'Sets up and runs the tests in Firefox', ['test:conf', 'express:inline', 'webdriver_jasmine_runner:firefox']
   grunt.registerTask 'test:server', 'Starts a Jasmine server at localhost:8890', ['express:server', 'express-keepalive']

+  _ = grunt.util._
   spec = (grunt.option('spec') || grunt.option('jsspec') || '*').replace(/(Spec|Test)$/, '')
   debug = grunt.option 'verbose' || false
   version = grunt.option 'version' || 'dev'
@@ -54,7 +55,6 @@
       target: ['target/']

     jshint:
-      files: ['grunt/**/*.js', 'src/apps/**/*.js']
       options:
         bitwise: true
         curly: true
@@ -70,6 +70,12 @@
         unused: 'vars'
         es3: true
         laxbreak: true
+      tasks:
+        files:
+          src: ['grunt/**/*.js']
+      apps:
+        files:
+          src: ['src/apps/**/*.js']

     "regex-check":
       x4:
@@ -214,9 +220,16 @@
         tasks: ['coffee']
         options:
           spawn: false
-      src:
+      apps:
         files: 'src/apps/**/*.js'
-        tasks: ['jshint']
+        tasks: ['jshint:apps']
+        options:
+          spawn: false
+      tasks:
+        files: 'grunt/tasks/**/*.js'
+        tasks: ['jshint:tasks']
+        options:
+          spawn: false
       styles:
         files: 'src/apps/**/*.css'
         tasks: ['css']
@@ -261,13 +274,20 @@
   # Only recompile changed coffee files
   changedFiles = Object.create null

-  onChange = grunt.util._.debounce ->
-    grunt.config ['coffee', 'test'],
-      expand: true
-      cwd: 'test/spec'
-      src: grunt.util._.map(Object.keys(changedFiles), (filepath) -> filepath.replace('test/spec/', ''))
-      dest: 'test/gen'
-      ext: '.js'
+  onChange = _.debounce ->
+    specFiles = []
+    taskFiles = []
+    appsFiles = []
+
+    _.each Object.keys(changedFiles), (filepath) ->
+      specFiles.push(filepath) if _.contains(filepath, 'spec')
+      taskFiles.push(filepath) if _.contains(filepath, 'tasks')
+      appsFiles.push(filepath) if _.contains(filepath, 'apps')
+
+    grunt.config ['coffee', 'test', 'src'], specFiles
+    grunt.config ['jshint', 'tasks', 'files', 'src'], taskFiles
+    grunt.config ['jshint', 'apps', 'files', 'src'], appsFiles
+
     changedFiles = Object.create null
   , 200


For consistency, also precompress css files

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -133,8 +133,10 @@
           'build/easyXDM/easyXDM.min.js':                    ['build/js/jail_iframe/libs/easyXDM.js']
           'build/server/nu.min.js':                          ['build/nu.js']
     shell:
-      gzip_files:
+      gzip_js_files:
         command: 'find build/ -iname \'*.js\'  -print0 | xargs -0 -n 1 gzip -kf -9'
+      gzip_css_files:
+        command: 'find build/ -iname \'*.css\'  -print0 | xargs -0 -n 1 gzip -kf -9'

     copy:
       build:
@@ -149,7 +151,9 @@
         files: [
           { src: ['*.js', 'server/**/*.js', 'easyXDM/*.js',
                   '*.js.gz', 'server/**/*.js.gz', 'easyXDM/*.js.gz',
-                  '**/*.css', '**/*.png', '**/*.gif', 'robots.txt'], cwd: 'build', dest: 'dist', expand: true }
+                  '**/*.css',
+                  '**/*.css.gz',
+                  '**/*.png', '**/*.gif', 'robots.txt'], cwd: 'build', dest: 'dist', expand: true }
           { src: ['**/*.png', '**/*.gif', 'robots.txt'], cwd: 'build', dest: 'dist/server', expand: true }
         ]
     watch:
@@ -211,7 +215,7 @@

   grunt.registerTask 'jail_iframe', ['concat:jail_iframe', 'code_inliner:basic_css', 'uglify:jail_iframe']
   grunt.registerTask 'compile', ['clean', 'copy:build', 'copy:start_stop_files', 'coffee', 'sass', 'cssUrlEmbed', 'cssmin',
-                                 'jail_iframe', 'concat', 'uglify:all_except_jail_iframe', 'code_inliner', 'shell:gzip_files', 'copy:dist']
+                                 'jail_iframe', 'concat', 'uglify:all_except_jail_iframe', 'code_inliner', 'shell:gzip_css_files', 'shell:gzip_js_files', 'copy:dist']
   grunt.registerTask 'test',    ['jshint', 'qunit']

   grunt.registerTask 'default', ['compile', 'test']


copy icon font in build process

copy icon font in build process

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -146,7 +146,7 @@
     copy:
       build:
         files: [
-          { src: ['**/*.js', '**/*.png', '**/*.gif', 'robots.txt'], cwd: 'app', dest: 'build', expand: true }
+          { src: ['**/*.js', '**/*.png', '**/*.gif', '**/*.woff', 'robots.txt'], cwd: 'app', dest: 'build', expand: true }
         ]
       extension_events:
         files: [


no source maps for now

no source maps for now

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -26,8 +26,6 @@
         ]
     coffee:
       source:
-        options:
-          sourceMap: true
         files: [
           expand: true
           flatten: true
@@ -68,7 +66,7 @@
   grunt.loadNpmTasks 'grunt-contrib-requirejs'
   grunt.loadTasks 'tasks'

-  grunt.registerTask "default", ['sass', 'concat:css', 'coffee', 'requirejs-optimize-config', 'requirejs']
+  grunt.registerTask "default", ['sass', 'concat:css', 'coffee', 'requirejs-optimize-config', 'requirejs', 'copy:dist']

   grunt.registerTask 'requirejs-optimize-config', 'Builds the mainConfigFile for r.js', ->
     config_script = grunt.file.read('build/requirejs_config.js')


Copy stylesheets when making a distribution

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -65,6 +65,7 @@
                 files: [{
                     expand: true
                     src: [
+                        'css/**'
                         'image/**'
                         'slides/**'
                         'bower_components/**'


Clean up Grunt test tasks to reflect new test structure.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -9,10 +9,6 @@

   # Project configuration.
   grunt.initConfig
-    mocha:
-      default:
-        src: ['test/*.coffee']
-
     coffee:
       default:
         files: [
@@ -39,8 +35,6 @@
   # Default task.
   grunt.registerTask 'default', ['coffee', 'test']

-  grunt.registerTask 'test', 'mocha:default'
-
   grunt.registerTask 'nuke', "Nuke stuff. Don't run this unless you know what you're doing.", ->
     steps = [
       'rm -rf ~/.impromptu'
@@ -50,8 +44,10 @@

     exec steps.join ' && '

-  # Task for running Mocha tests with coffee.
-  grunt.registerMultiTask 'mocha', 'Run mocha unit tests.', ->
+  # Run the unit tests by spawning an Impromptu server and passing through the 'test' message.
+  # This allows us to run the tests in a real-world environment: the database requires an
+  # Impromptu server and worker process to work correctly.
+  grunt.registerTask 'test', 'Run unit tests.', ->
     done = @async()

     cmd = "IMPROMPTU_PORT=2934 impromptu server"


add lang task

add lang task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,127 +1,157 @@
+glob = require 'glob'
 module.exports = (grunt) ->
-    # Project configuration.
-    grunt.initConfig
-        pkg: grunt.file.readJSON 'package.json'
+  # Project configuration.
+  grunt.initConfig
+      pkg: grunt.file.readJSON 'package.json'

-        jshint:
-            core: ['dev/src/*.js', '!dev/src/_*.js']
-            plugins: ['dev/src/*.js', '!dev/src/_*.js', 'dev/plugins/*.js']
-            mixing: ['dev/src/*.js', '!dev/src/_*.js',
-                'dev/plugins/mixing.enchant.js']
-            options:
-                curly: true
-                eqeqeq: true
-                immed: true
-                latedef: true
-                newcap: false
-                noarg: true
-                sub: true
-                undef: true
-                boss: true
-                eqnull: true
-                browser: true
-                proto: true
-                multistr: true
-                globals:
-                    enchant: true
-                    webkitAudioContext: true
-                    DocumentFragment: true
-                    WebGLBuffer: true
-                    gl: true
-                    Ammo: true
-                    vec2: true
-                    vec3: true
-                    vec4: true
-                    mat2: true
-                    mat3: true
-                    mat4: true
-                    quat4: true
+      jshint:
+        core: ['dev/src/*.js', '!dev/src/_*.js']
+        plugins: ['dev/src/*.js', '!dev/src/_*.js', 'dev/plugins/*.js']
+        mixing: ['dev/src/*.js', '!dev/src/_*.js',
+          'dev/plugins/mixing.enchant.js']
+        options:
+          curly: true
+          eqeqeq: true
+          immed: true
+          latedef: true
+          newcap: false
+          noarg: true
+          sub: true
+          undef: true
+          boss: true
+          eqnull: true
+          browser: true
+          proto: true
+          multistr: true
+          globals:
+            enchant: true
+            webkitAudioContext: true
+            DocumentFragment: true
+            WebGLBuffer: true
+            gl: true
+            Ammo: true
+            vec2: true
+            vec3: true
+            vec4: true
+            mat2: true
+            mat3: true
+            mat4: true
+            quat4: true

-        concat:
-            dist:
-                src: [
-                    'dev/src/_intro.js'
-                    'dev/src/header.js'
-                    'dev/src/Class.js'
-                    'dev/src/Env.js'
-                    'dev/src/Event.js'
-                    'dev/src/EventTarget.js'
-                    'dev/src/Core.js'
-                    'dev/src/Game.js'
-                    'dev/src/Node.js'
-                    'dev/src/Entity.js'
-                    'dev/src/Sprite.js'
-                    'dev/src/Label.js'
-                    'dev/src/Map.js'
-                    'dev/src/Group.js'
-                    'dev/src/Matrix.js'
-                    'dev/src/DetectColorManager.js'
-                    'dev/src/DomManager.js'
-                    'dev/src/DomLayer.js'
-                    'dev/src/CanvasLayer.js'
-                    'dev/src/Scene.js'
-                    'dev/src/CanvasScene.js'
-                    'dev/src/DOMScene.js'
-                    'dev/src/Surface.js'
+      concat:
+        dist:
+          src: [
+            'dev/src/_intro.js'
+            'dev/src/header.js'
+            'dev/src/Class.js'
+            'dev/src/Env.js'
+            'dev/src/Event.js'
+            'dev/src/EventTarget.js'
+            'dev/src/Core.js'
+            'dev/src/Game.js'
+            'dev/src/Node.js'
+            'dev/src/Entity.js'
+            'dev/src/Sprite.js'
+            'dev/src/Label.js'
+            'dev/src/Map.js'
+            'dev/src/Group.js'
+            'dev/src/Matrix.js'
+            'dev/src/DetectColorManager.js'
+            'dev/src/DomManager.js'
+            'dev/src/DomLayer.js'
+            'dev/src/CanvasLayer.js'
+            'dev/src/Scene.js'
+            'dev/src/CanvasScene.js'
+            'dev/src/DOMScene.js'
+            'dev/src/Surface.js'

-                    # sound support
-                    'dev/src/DOMSound.js'
-                    'dev/src/WebAudioSound.js'
-                    'dev/src/Sound.js'
+            # sound support
+            'dev/src/DOMSound.js'
+            'dev/src/WebAudioSound.js'
+            'dev/src/Sound.js'

-                    # animation feature
-                    'dev/src/Easing.js'
-                    'dev/src/ActionEventTarget.js'
-                    'dev/src/Timeline.js'
-                    'dev/src/Action.js'
-                    'dev/src/ParallelAction.js'
-                    'dev/src/Tween.js'
+            # animation feature
+            'dev/src/Easing.js'
+            'dev/src/ActionEventTarget.js'
+            'dev/src/Timeline.js'
+            'dev/src/Action.js'
+            'dev/src/ParallelAction.js'
+            'dev/src/Tween.js'

-                    'dev/src/_outro.js'
-                ]
-                dest: 'dev/enchant.js'
-                options:
-                    banner:
-                        """
-                        /**
-                         * <%= pkg.name %> v<%= pkg.version %>
-                         * <%= pkg.homepage %>
-                         *
-                         * Copyright Ubiquitous Entertainment Inc.
-                         * Released under the MIT license.
-                         */\n\n
-                        """
+            'dev/src/_outro.js'
+          ]
+          dest: 'dev/enchant.js'
+          options:
+            banner: """
+                                                            /**
+                                                             * <%= pkg.name %> v<%= pkg.version %>
+                                                             * <%= pkg.homepage %>
+                                                             *
+                                                             * Copyright Ubiquitous Entertainment Inc.
+                                                             * Released under the MIT license.
+                                                             */\n\n
+                                                            """

-        uglify:
-            dist:
-                files:
-                    'enchant.min.js': ['dev/enchant.js']
-                options:
-                    banner: "/* <%= pkg.name %> v<%= pkg.version %> <%= pkg.homepage %> Copyright (c) Ubiquitous Entertainment Inc. Released Under the MIT license. */\n"
+      uglify:
+        dist:
+          files:
+            'enchant.min.js': ['dev/enchant.js']
+          options:
+            banner: "/* <%= pkg.name %> v<%= pkg.version %> <%= pkg.homepage %> Copyright (c) Ubiquitous Entertainment Inc. Released Under the MIT license. */\n"

-        qunit:
-            files: ['tests/qunit/*/*.html']
+      qunit:
+        files: ['tests/qunit/*/*.html']

-        exec:
-            lang:
-                command: 'rake lang'
-            doc:
-                command: 'rake doc'
+      exec:
+        lang:
+          command: 'rake lang'
+        doc:
+          command: 'rake doc'

-        watch:
-            core:
-                files: ['dev/src/*.js']
-                tasks: ['concat', 'uglify']
-            plugins:
-                files: ['dev/src/*.js', 'dev/plugins/*.js']
-                tasks: ['jshint', 'concat', 'uglify', 'exec:lang']

-    grunt.loadNpmTasks 'grunt-contrib-jshint'
-    grunt.loadNpmTasks 'grunt-contrib-concat'
-    grunt.loadNpmTasks 'grunt-contrib-uglify'
-    grunt.loadNpmTasks 'grunt-contrib-qunit'
-    grunt.loadNpmTasks 'grunt-exec'

-    # Default task.
-    grunt.registerTask 'default', [
-        'jshint:core', 'concat', 'uglify', 'qunit', 'exec:lang']
+      watch:
+        core:
+          files: ['dev/src/*.js']
+          tasks: ['concat', 'uglify']
+        plugins:
+          files: ['dev/src/*.js', 'dev/plugins/*.js']
+          tasks: ['jshint', 'concat', 'uglify', 'exec:lang']
+
+  grunt.loadNpmTasks 'grunt-contrib-jshint'
+  grunt.loadNpmTasks 'grunt-contrib-concat'
+  grunt.loadNpmTasks 'grunt-contrib-uglify'
+  grunt.loadNpmTasks 'grunt-contrib-qunit'
+  grunt.loadNpmTasks 'grunt-exec'
+
+  # Default task.
+  grunt.registerTask 'default', [
+    'jshint:core', 'concat', 'uglify', 'qunit', 'exec:lang']
+
+  grunt.registerTask 'lang', 'Make lang file.', ()->
+    grunt.log.writeln 'processing..'
+    done = this.async;
+    fs = require('fs')
+
+    (glob.sync "dev/{plugins\/,}*.js").forEach (source)->
+      dist_en = source.toString().replace(/dev\//, './')
+      dist_ja = source.toString().replace(/dev\//, './ja/')
+      dist_de = source.toString().replace(/dev\//, './de/')
+
+      fs.writeFileSync dist_en, (fs.readFileSync source).toString()
+      .replace(/^[\*\s]+\[lang:en\]\n(\s*[\s\S]*?\n)[\*\s]*\[\/lang\]\s*\n/mg, '$1')
+      .replace(/^[\*\s]+\[lang:ja\]\n(\s*[\s\S]*?\n)[\*\s]*\[\/lang\]\s*\n/mg, '')
+      .replace(/^[\*\s]+\[lang:de\]\n(\s*[\s\S]*?\n)[\*\s]*\[\/lang\]\s*\n/mg, '')
+
+      fs.writeFileSync dist_ja, (fs.readFileSync source).toString()
+      .replace(/^[\*\s]+\[lang:en\]\n(\s*[\s\S]*?\n)[\*\s]*\[\/lang\]\s*/mg, '')
+      .replace(/^[\*\s]+\[lang:ja\]\n(\s*[\s\S]*?\n)[\*\s]*\[\/lang\]\s*/mg, '$1')
+      .replace(/^[\*\s]+\[lang:de\]\n(\s*[\s\S]*?\n)[\*\s]*\[\/lang\]\s*/mg, '')
+
+      fs.writeFileSync dist_de, (fs.readFileSync source).toString()
+      .replace(/^[\*\s]+\[lang:en\]\n(\s*[\s\S]*?\n)[\*\s]*\[\/lang\]\s*\n/mg, '')
+      .replace(/^[\*\s]+\[lang:ja\]\n(\s*[\s\S]*?\n)[\*\s]*\[\/lang\]\s*\n/mg, '')
+      .replace(/^[\*\s]+\[lang:de\]\n(\s*[\s\S]*?\n)[\*\s]*\[\/lang\]\s*\n/mg, '$1')
+
+      grunt.log.writeln(source)
+    grunt.log.writeln 'processing..'


Reduce max cyclomatic complexity to 14

Reduce max cyclomatic complexity to 14

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -88,7 +88,7 @@
           value: 120
         cyclomatic_complexity:
           level: 'error'
-          value: 13
+          value: 14

       source: ['src/**/*.coffee']
       grunt: 'Gruntfile.coffee'


It is no longer necessary to remove dist/test on build

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -214,7 +214,6 @@
       directories:
         src: [
           'dist/styles'
-          'dist/test'
           'dist/**/*'
         ]
         filter: (filepath) ->


Fix issue #920: create project values on empty projects

--- app/coffee/modules/admin/project-values.coffee
+++ app/coffee/modules/admin/project-values.coffee
@@ -180,7 +180,9 @@
             return if not form.validate()

             $scope.newValue.project = $scope.project.id
-            $scope.newValue.order = $scope.maxValueOrder + 1
+
+            $scope.newValue.order = if $scope.maxValueOrder then $scope.maxValueOrder + 1 else 1
+
             promise = $repo.create(valueType, $scope.newValue)
             promise.then =>
                 $ctrl.loadValues().then ->


fix grunt

fix grunt

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -91,7 +91,7 @@
                 message: 'Built from %sourceCommit% on branch %sourceBranch%'
             pages:
                 options:
-                    remote: 'git@github.com:veggiemonk/slide-immutable-js.git'
+                    remote: 'git@github.com:veggiemonk/slides_gitlab_presentation.git'
                     branch: 'gh-pages'


update watch task [ci skip]

update watch task [ci skip]

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -98,7 +98,7 @@
     watch:
       all:
         files: ['<%= srcFolder %>/**/*.{coffee,eco,scss}']
-        tasks: 'default'
+        tasks: 'all'

     # # To test, run `grunt --no-write -v release`
     release:


Fix markup build step

Fix markup build step

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -225,7 +225,7 @@

     dom_munger:
       link:
-        src: 'public/index.html'
+        src: 'app/index.html'
         dest: 'public/index.html'
         options:
           append: [
@@ -274,10 +274,13 @@

     jade:
       options:
-        basedir: 'app/'
-      default:
-        files:
-          'staging/index.html': 'app/index.jade'
+        # Let downstream build step handles compression when building for
+        # production
+        pretty: true
+      default:
+        files:
+          # Use the Jade include system, so only include `index.jade` here
+          'staging/index.html': 'app/jade/index.jade'

     ## Script-specific


Fix Page#find() and friends.

Fix Page#find() and friends.

Don't override wesabe.xpath using wesabe.provide, otherwise we never
get Pathway and Pathset as part of the export.

--- application/chrome/content/wesabe/xpath.coffee
+++ application/chrome/content/wesabe/xpath.coffee
@@ -243,4 +243,4 @@
 #   wesabe.xpath.Pathway.bind(xpath, binding)
 #
 # We re-add the original here with a deprecation notice.
-wesabe.provide 'xpath.bind', logger.wrapDeprecated('wesabe.xpath.bind', 'wesabe.xpath.Pathway.bind', (args...) -> Pathway.bind args...)
+module.exports.bind = logger.wrapDeprecated('wesabe.xpath.bind', 'wesabe.xpath.Pathway.bind', (args...) -> Pathway.bind args...)


parseResources @wsorenson

parseResources @wsorenson

--- SingularityUI/app/collections/TasksActive.coffee
+++ SingularityUI/app/collections/TasksActive.coffee
@@ -8,7 +8,7 @@
         _.each tasks, (task, i) =>
             task.id = task.taskId.id
             task.name = task.mesosTask.name
-            task.resources = task.taskRequest.request.resources
+            task.resources = @parseResources task
             task.memoryHuman = if task.resources?.memoryMb? then "#{ task.resources.memoryMb }Mb" else ''
             task.host = task.offer.hostname?.split('.')[0]
             task.startedAt = task.taskId.startedAt
@@ -19,6 +19,10 @@

         tasks

+    parseResources: (task) ->
+        cpus: _.find(task.mesosTask.resources, (resource) -> resource.name is 'cpus')?.scalar?.value ? ''
+        memoryMb: _.find(task.mesosTask.resources, (resource) -> resource.name is 'mem')?.scalar?.value ? ''
+
     comparator: 'startedAt'

 module.exports = TasksActive


define noop for JGuest#fetchNonce

define noop for JGuest#fetchNonce

--- client/app/MainApp/account-mixins.coffee
+++ client/app/MainApp/account-mixins.coffee
@@ -1,7 +1,9 @@
 AccountMixin = do ->

   init:(api)->
-    {JAccount} = api
+    {JAccount, JGuest} = api
+
+    JGuest::fetchNonce = ->

     nonces = []


updated to https

updated to https

--- lib/MainView.coffee
+++ lib/MainView.coffee
@@ -30,7 +30,7 @@
     @webview.attr("src", url)

   setSearchText: (text) ->
-    url = 'http://unbug.github.io/codelf/'
+    url = 'https://unbug.github.io/codelf/'
     url = (url+'#'+text) if text&&text.length

     # http://superuser.com/questions/206229/how-to-make-a-blank-page-in-google-chrome-at-start-up


don't cherry-pick modules, because it can cause crazy bugs

--- workers/auth/lib/auth/bongo.coffee
+++ workers/auth/lib/auth/bongo.coffee
@@ -14,14 +14,7 @@
 module.exports = new Bongo {
   mongo
   root: projectRoot
-  models: [
-    'workers/social/lib/social/models/session.coffee'
-    'workers/social/lib/social/models/account'
-    'workers/social/lib/social/models/group'
-    'workers/social/lib/social/models/name.coffee'
-    'workers/social/lib/social/models/vm.coffee'
-    # 'workers/social/lib/social/models/guest.coffee'
-  ]
+  models: 'workers/social/lib/social/models'
   mq: new Broker mqOptions
   resourceName: authWorker.queueName
 }


:droplet:

:droplet:

Changes gh-pages search to match the correct way that  index gh-pages

--- scripts/cfpbsearch.coffee
+++ scripts/cfpbsearch.coffee
@@ -36,12 +36,12 @@
     postSearch(msg, msg.match[2], msg.match[1])

   robot.respond /search (ghpage|ghpages|gh-pages) (.*)$/i, (msg) ->
-    postSearch(msg, msg.match[2], "gh page")
+    postSearch(msg, msg.match[2], "gh_page")

   # hate using this negative lookahead, but couldn't figure out a better way to get it to NOT trip this search
   # without this, searching specific sources such as hubot search wiki <term> would also trigger this response
   robot.respond /search (?!(next|wiki|readme|issue|ghpage|ghpages|gh-pages|help))\s?(.*)$/i, (msg) ->
-    postSearch(msg, msg.match[2], "wiki,readme,gh page")
+    postSearch(msg, msg.match[2], "wiki,readme,gh_page")

   robot.respond /search next$/i, (msg) ->
     if !hasSearch(msg)


Fix logs

Fix logs

--- server/testSystems/Informatics.coffee
+++ server/testSystems/Informatics.coffee
@@ -168,7 +168,7 @@
         informaticsProblemId = @_informaticsProblemId(problemId)
         success = false
         for i in [1..8]
-            logger.info "Try submit #{user}, #{problem}, attempt #{i}"
+            logger.info "Try submit #{user}, #{problemId}, attempt #{i}"
             try
                 oldSubmits = await Submit.findByUserAndProblem(user.informaticsId, problemId)
                 try
@@ -177,14 +177,14 @@
                 finally
                     await downloadSubmits.runForUser(user.informaticsId, 5, 1)
             catch e
-                logger.info "Error submitting #{user} #{problem} attempt #{i}: ", e.message
+                logger.info "Error submitting #{user} #{problemId} attempt #{i}: ", e.message
             newSubmits = await Submit.findByUserAndProblem(user.informaticsId, problemId)
             if oldSubmits.length != newSubmits.length
-                logger.info "However, submit #{user} #{problem} appeared after downloadSubmits from attempt #{i}"
+                logger.info "However, submit #{user} #{problemId} appeared after downloadSubmits from attempt #{i}"
                 success = true
                 break
         if not success
-            throw "Can't submit #{user} #{problem}"
+            throw "Can't submit #{user} #{problemId}"

     registerUser: (user) ->
         logger.info "Moving user #{user._id} to unknown group"


Remove Serrano version check from exporter collection model

--- src/coffee/cilantro/models/exporter.coffee
+++ src/coffee/cilantro/models/exporter.coffee
@@ -10,13 +10,6 @@
     class ExporterCollection extends base.Collection
         model: ExporterModel

-        # Versions greater than or equal to this version are considered to
-        # support notification on completion.
-        minSerranoVersionProgressFeature: '2.0.16'
-
-        notifiesOnComplete: ->
-            c.isSupported(@minSerranoVersionProgressFeature)
-
         parse: (attrs) ->
             if attrs? and attrs._links?
                 # Ignore the exporter endpoint itself


better diff between days

better diff between days

--- lib/eslogger.coffee
+++ lib/eslogger.coffee
@@ -256,7 +256,7 @@
     time = moment().utc().format('HH:mm')
     start_date = start.format('MMM, ddd Do')
     stop_date = stop.format('HH:mm')
-    diffdays = moment().utc().diff(stop, 'days')
+    diffdays = moment().utc().diff(start, 'days')
     content = @html_head(room)
     nav = " - Day before"
     if diffdays isnt 0


Removed fastchat file.

Removed fastchat file.

--- lib/fastchat.coffee
+++ lib/fastchat.coffee
@@ -1,23 +0,0 @@
-# exports
-
-###
-Routes
-###
-exports.DeviceRoutes = require './routes/device'
-exports.GroupRoutes = require './routes/group'
-exports.MessageRoutes = require './routes/message'
-exports.UserRoutes = require './routes/user'
-
-###
-Models
-###
-exports.Device = require './model/device'
-exports.Group = require './model/group'
-exports.GroupSetting = require './model/groupSetting'
-exports.Message = require './model/message'
-exports.User = require './model/user'
-
-###
-Socket.io
-###
-exports.Socket = require './socket/socket'


Add index for MessagesDiff

Add index for MessagesDiff

--- lib/messages.coffee
+++ lib/messages.coffee
@@ -52,6 +52,11 @@
   ## This index makes findMessageParent fast.
   Messages._ensureIndex [
     ['children', 1]
+  ]
+  ## Diffs are accessed by message ID, and then sorted by updated date.
+  MessagesDiff._ensureIndex [
+    ['id', 1]
+    ['updated', 1]
   ]

 @rootMessages = (group) ->


cleanup further

cleanup further

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -5,18 +5,11 @@
 {inspect} = require 'util'
 {
   haveSomeNonEmptySelection
-  highlightRanges
   isEndsWithNewLineForBufferRow
   getValidVimBufferRow
   cursorIsAtEmptyRow
-  getVisibleBufferRange
   getWordPatternAtBufferPosition
   destroyNonLastSelection
-
-  selectedRange
-  selectedText
-  toString
-  debug
 } = require './utils'
 swrap = require './selection-wrapper'
 settings = require './settings'
@@ -45,6 +38,9 @@
   isOccurrence: ->
     @occurrence

+  setOccurrence: (@occurrence) ->
+    @occurrence
+
   setMarkForChange: (range) ->
     @vimState.mark.setRange('[', ']', range)

@@ -79,9 +75,16 @@

     @initialize()

-    @onDidSetOperatorModifier ({occurrence, wise}) =>
-      @wise = wise if wise?
-      @setOccurrence('modifier') if occurrence?
+    @onDidSetOperatorModifier (options) =>
+      if options.wise?
+        @wise = options.wise
+
+      if options.occurrence?
+        @setOccurrence(options.occurrence)
+        if @isOccurrence()
+          # Reset existing preset-occurrence. e.g. `c o p`, `d o f`
+          @occurrenceManager.resetPatterns()
+          @addOccurrencePattern()

     @target ?= implicitTarget if implicitTarget = @getImplicitTarget()

@@ -89,10 +92,10 @@
       @setTarget(@new(@target))

     # When preset-occurrence was exists, auto enable occurrence-wise
-    if @occurrence
-      @setOccurrence('static')
-    else if @acceptPresetOccurrence and @occurrenceManager.hasPatterns()
-      @setOccurrence('preset')
+    if @acceptPresetOccurrence and @occurrenceManager.hasPatterns()
+      @setOccurrence(true)
+    if @isOccurrence()
+      @addOccurrencePattern() unless @occurrenceManager.hasMarkers()

     if @acceptPersistentSelection
       @subscribe @onDidDeactivateMode ({mode}) =>
@@ -114,21 +117,6 @@
     @vimState.hasPersistentSelections() and
     settings.get('autoSelectPersistentSelectionOnOperate')

-  # type is one of ['preset', 'modifier']
-  setOccurrence: (type) ->
-    @occurrence = true
-    switch type
-      when 'static'
-        unless @isComplete() # we enter operator-pending
-          debug 'static: mark as we enter operator-pending'
-          @addOccurrencePattern() unless @occurrenceManager.hasMarkers()
-      when 'preset'
-        debug 'preset: nothing to do since we have markers already'
-      when 'modifier'
-        debug 'modifier: overwrite existing marker when manually typed `o`'
-        @occurrenceManager.resetPatterns() # clear existing marker
-        @addOccurrencePattern() # mark cursor word.
-
   addOccurrencePattern: (pattern=null) ->
     pattern ?= @patternForOccurrence
     unless pattern?
@@ -164,7 +152,9 @@
     @activateMode('normal')

   selectOccurrence: ->
-    @addOccurrencePattern() unless @occurrenceManager.hasMarkers()
+    # In `.` repeated case, initailize() is never called so need to set here.
+    if @isRepeated() and not @occurrenceManager.hasMarkers()
+      @addOccurrencePattern()

     # To repoeat(`.`) operation where multiple occurrence patterns was set.
     # Here we save patterns which resresent unioned regex which @occurrenceManager knows.
@@ -181,21 +171,17 @@

   # Return true unless all selection is empty.
   selectTarget: ->
-    isSelect = @instanceof('Select')
-    useMarker = @needStay() and @stayByMarker
-    @mutationManager.init({isSelect, useMarker})
+    @mutationManager.init
+      isSelect: @instanceof('Select')
+      useMarker: @needStay() and @stayByMarker
     @mutationManager.setCheckPoint('will-select')

-    @target.forceWise(@wise) if @wise and @target.isMotion()
+    # Currently only motion have forceWise methods
+    @target.forceWise?(@wise) if @wise?
     @emitWillSelectTarget()

-    # To use CURRENT cursor position, this has to be BEFORE @target.select() which move cursors.
-    if @isOccurrence() and not @occurrenceManager.hasMarkers()
-      @addOccurrencePattern()
-
     @target.select()
-    if @isOccurrence()
-      @selectOccurrence()
+    @selectOccurrence() if @isOccurrence()

     if haveSomeNonEmptySelection(@editor) or @target.getName() is "Empty"
       @mutationManager.setCheckPoint('did-select')


Add missing translation

Add missing translation

--- src/javascripts/extensions/download.coffee
+++ src/javascripts/extensions/download.coffee
@@ -48,7 +48,7 @@

   buttonHtml: ->
     """
-    <button class="download-button" title="Download episode"></button>
+    <button class="download-button" title="#{@t('download.episode')}"></button>
     """

   panelHtml: ->


Add function completions

Add function completions

--- lib/provider.coffee
+++ lib/provider.coffee
@@ -11,18 +11,17 @@
     if isCompletingInheritedClass = @isCompletingInheritedClass({scopeDescriptor})
       completions = completions.concat(@getClassCompletions({prefix}))

-    else if isCompletingConstantOrClass = @isCompletingConstantOrClass({scopeDescriptor})
+    else if isCompletingConstant = @isCompletingConstant({scopeDescriptor})
       completions = completions.concat(@getClassCompletions({prefix}))
       completions = completions.concat(@getConstantCompletions({prefix}))
+      completions = completions.concat(@getFunctionCompletions({prefix}))

-    else if isCompletingMethod = @isCompletingMethod({scopeDescriptor})
-        completions = completions.concat(@getMethodCompletions({scopeDescriptor, prefix}))
+    # else if isCompletingMethod = @isCompletingMethod({scopeDescriptor})
+    #     completions = completions.concat(@getMethodCompletions({scopeDescriptor, prefix}))

     else
-      # Add methods, functions, and classes
+      # Add methods
       completions = completions.concat(@getMethodCompletions({scopeDescriptor, prefix}))
-      completions = completions.concat(@getFunctionCompletions({scopeDescriptor, prefix}))
-      # completions = completions.concat(@getClassCompletions({scopeDescriptor, prefix}))

     completions.sort()
     # @getConstructCompletions(request)
@@ -41,7 +40,7 @@
     scopes = scopeDescriptor.getScopesArray()
     return hasScope(scopes, 'meta.function.method')

-  isCompletingConstantOrClass: ({scopeDescriptor}) ->
+  isCompletingConstant: ({scopeDescriptor}) ->
     scopes = scopeDescriptor.getScopesArray()
     return hasScope(scopes, 'support.class.ruby') || hasScope(scopes, 'variable.other.constant.ruby')

@@ -49,11 +48,11 @@
     scopes = scopeDescriptor.getScopesArray()
     return hasScope(scopes, 'entity.other.inherited-class.ruby')

-  getCompletions: ({prefix}) ->
-    completions = []
-    for constant, type of @constants when stringContains(prefix, constant)
-      completions.push(@buildConstantCompletion(prefix, constant))
-    completions
+  # getCompletions: ({prefix}) ->
+  #   completions = []
+  #   for constant, type of @constants when stringContains(prefix, constant)
+  #     completions.push(@buildConstantCompletion(prefix, constant))
+  #   completions

   getMethodCompletions: ({scopeDescriptor, prefix}) ->
     completions = []
@@ -72,6 +71,25 @@
     for constant, type of @constants when stringContains(prefix, constant)
       completions.push(@buildConstantCompletion(prefix, constant))
     completions
+
+  getFunctionCompletions: ({prefix}) ->
+    completions = []
+    for func, {args} of @functions when stringContains(prefix, func)
+      completions.push(@buildFunctionCompletion(prefix, func, args))
+    completions
+
+  buildFunctionCompletion: (prefix, func, args) ->
+    snippet = "#{func.titleize}("
+    i = 0
+    for key of args
+      snippet += "${#{i + 1}:#{key}}"
+      snippet += if i+1 == Object.keys(args).length then ')' else ', '
+      i++
+
+    type: 'function'
+    snippet: snippet
+    displayText: func
+    replacementPrefix: prefix

   buildClassCompletion: (prefix, klass) ->
     type: 'class'


Add { as alignable character (disabled by default)

--- lib/provider.coffee
+++ lib/provider.coffee
@@ -2,6 +2,10 @@
   selector: ['.source.css.scss', '.source.sass']
   id: 'aligner-scss' # package name
   config:
+    ':-enabled':
+      title: 'Enable aligning :'
+      type: 'boolean'
+      default: true
     ':-alignment':
       title: 'Padding for :'
       description: 'Pad left or right of the character'
@@ -17,8 +21,25 @@
       description: 'Add 1 whitespace to the right'
       type: 'boolean'
       default: true
-    ':-scope':
-      title: 'Character scope'
-      description: 'Scope string to match'
+    '{-enabled':
+      title: 'Enable aligning {'
+      type: 'boolean'
+      default: false
+    '{-alignment':
+      title: 'Padding for {'
+      description: 'Pad left or right of the character'
       type: 'string'
-      default: 'key-value|property-name|operator'
+      default: 'left'
+    '{-leftSpace':
+      title: 'Left space for {'
+      description: 'Add 1 whitespace to the left'
+      type: 'boolean'
+      default: true
+    '{-rightSpace':
+      title: 'Right space for {'
+      description: 'Add 1 whitespace to the right'
+      type: 'boolean'
+      default: true
+  privateConfig:
+    ':-scope': 'key-value|property-name|operator'
+    '{-scope': 'property-list.begin'


fix #29

fix #29

--- lib/refactor.coffee
+++ lib/refactor.coffee
@@ -46,7 +46,7 @@

   onRename: (e) =>
     isExecuted = false
-    for watcher in @watchers
+    @watchers.forEach (watcher) ->
       isExecuted or= watcher.rename()
     d 'rename', isExecuted
     return if isExecuted
@@ -54,7 +54,7 @@

   onDone: (e) =>
     isExecuted = false
-    for watcher in @watchers
+    @watchers.forEach (watcher) ->
       isExecuted or= watcher.done()
     return if isExecuted
     e.abortKeyBinding()


Make callbacks optional

Make callbacks optional

--- lib/snippets.coffee
+++ lib/snippets.coffee
@@ -42,10 +42,10 @@
         @userSnippetsFile = new File(userSnippetsPath)
         @userSnippetsFile.on 'moved removed contents-changed', =>
           atom.syntax.removeProperties(userSnippetsPath)
-          @loadUserSnippets ->
+          @loadUserSnippets()
         @loadSnippetsFile(userSnippetsPath, callback)
       else
-        callback()
+        callback?()

   loadPackageSnippets: ->
     packages = atom.packages.getLoadedPackages()
@@ -57,25 +57,25 @@
     @loaded = true

   loadSnippetsDirectory: (snippetsDirPath, callback) ->
-    return callback() unless fs.isDirectorySync(snippetsDirPath)
+    return callback?() unless fs.isDirectorySync(snippetsDirPath)

     fs.readdir snippetsDirPath, (error, entries) =>
       if error?
         console.warn(error)
-        callback()
+        callback?()
       else
         paths = entries.map (file) -> path.join(snippetsDirPath, file)
         async.eachSeries(paths, @loadSnippetsFile.bind(this), callback)

   loadSnippetsFile: (filePath, callback) ->
-    return callback() unless CSON.isObjectPath(filePath)
+    return callback?() unless CSON.isObjectPath(filePath)

     CSON.readFile filePath, (error, object) =>
       if error?
         console.warn "Error reading snippets file '#{filePath}': #{error.stack ? error}"
       else
         @add(filePath, @translateTextmateSnippet(object))
-      callback()
+      callback?()

   translateTextmateSnippet: (snippet={}) ->
     {scope, name, content, tabTrigger} = snippet


payment: JPaymentSubscription#spend => #debit

--- workers/social/lib/social/models/payment/subscription.coffee
+++ workers/social/lib/social/models/payment/subscription.coffee
@@ -25,8 +25,8 @@
         'cancel'
         'resume'
         'calculateRefund'
-        'spend'
         'checkUsage'
+        'debit'
       ]
     schema          :
       uuid          : String
@@ -194,7 +194,7 @@
       then callback { message: 'quota exceeded' }, results[0]
       else callback null

-  spend: secure (client, options, callback) ->
+  debit: secure (client, pack, callback) ->
     JPaymentPlan = require './plan'

     { delegate } = client.connection
@@ -207,7 +207,7 @@
         return callback err  if err

         incOp = {}
-        for own key, val of options
+        for own key, val of pack.quantities
           if not (@usage.hasOwnProperty key) or plan[key] - @usage[key] - val < 0
             return callback { message: 'Quota exceeded', planCode: key }
           incOp["usage.#{ key }"] = val


CollaborativeWorkspace: Introduced onlineCountRef.

--- client/CollaborativeWorkspace/collaborativeworkspace.coffee
+++ client/CollaborativeWorkspace/collaborativeworkspace.coffee
@@ -36,6 +36,7 @@
     @usersRef       = @workspaceRef.child "users"
     @userRef        = @usersRef.child KD.nick()
     @requestPingRef = @workspaceRef.child "requestPing"
+    @onlineCountRef = @workspaceRef.child "onlineUserCount"
     @sessionKeysRef = @firebaseRef.child  "session_keys"

   syncWorkspace: ->
@@ -125,6 +126,13 @@
       @syncWorkspace()
       @connected = yes
       KD.utils.killWait @pingHostTimer
+
+    @usersRef.on "value", (snapshot) =>
+      data   = snapshot.val()
+      return unless data
+      count  = 0
+      count++ for username, state of data when state.status is "online"
+      @onlineCountRef.set count

   requestPingFromHost: ->
     @requestPingRef.set Date.now()


minor: typos fixed

minor: typos fixed

--- client/activity/lib/views/activityannouncementwidget.coffee
+++ client/activity/lib/views/activityannouncementwidget.coffee
@@ -17,7 +17,7 @@
     return """

New: Koding Hackathon is Back!

-
Win over $150,000 is cash prizes! Hack from where ever you are!

+
Win over $150,000 in cash prizes! Hack from wherever you are!

       Apply Now, space limited!
     """


Remove stray sales_order references

Remove stray sales_order references

--- client/skr/components/InvoiceFinder.cjsx
+++ client/skr/components/InvoiceFinder.cjsx
@@ -9,9 +9,6 @@
         autoFocus: true, name: 'visible_id', label: 'Invoice #'

     dataObjects:
-        sales_order: ->
-            @props.sales_order || new Skr.Models.Invoice
-
         query: ->
             new Lanes.Models.Query({
                 initialFieldIndex: 1
@@ -29,7 +26,6 @@
             })

     render: ->
-
         <LC.RecordFinder ref="finder" sm=2
             name='customer'
             {...@props}


storing the demo csv in a global object for debugging


REFACTOR: code cleanup

REFACTOR: code cleanup

--- source/Art/Engine/Core/Element.coffee
+++ source/Art/Engine/Core/Element.coffee
@@ -848,50 +848,15 @@
       @_currentToTargetMatrix = elementToTargetMatrix

       targetSpaceDrawArea = @drawAreaIn(elementToTargetMatrix).intersection target.getClippingArea()
-      @_cachedFullDraw targetSpaceDrawArea, target, elementToTargetMatrix if targetSpaceDrawArea.area > 0
+      return unless targetSpaceDrawArea.area > 0
+
+      @_cachedFullDraw targetSpaceDrawArea, target, elementToTargetMatrix
     finally
       @_currentDrawTarget = @_currentToTargetMatrix = null

   #################
   # Draw Caching
   #################
-
-  _resetDrawCache: ->
-    @_drawCacheBitmap = null
-    @_drawCacheToElementMatrix = null
-
-  _drawPropertiesChanged: ->
-    @_clearDrawCache()
-
-  _elementToParentMatrixChanged: (oldElementToParentMatrix)->
-
-  _needsRedrawing: (descendant = @) ->
-    @_clearDrawCache()
-    if @getPendingVisible() && @getPendingOpacity() > 1/512
-      @getPendingParent()?._needsRedrawing descendant
-
-  ###
-
-  When clearing drawCaching for this branch of the AIM, we stop recursing
-  down a sub-branch when we hit an existing drawCache. When a drawCache is
-  created, all its children's drawCaches are removed. Therefor when a
-  drawCache exists, all children are drawCache free.
-
-  ###
-  __clearDrawCacheCallbackFromDrawCacheManager: ->
-    @_resetDrawCache()
-
-  _clearDrawCache: ->
-    return unless @_drawCacheBitmap
-    drawCacheManager.doneWithCacheBitmap @
-    true
-
-  _releaseAllCacheBitmaps: ->
-    count = 0
-    count++ if @_clearDrawCache()
-    count += child._releaseAllCacheBitmaps() for child in @_children
-    count
-
   ###
   "pixel-exact-caching"

@@ -907,17 +872,47 @@
       use pixel-exact cache

   Additional options:
-    We may add another option which lets of add a "cache-at" scale factor to force lower or
-    higher resolution caching.
-
-  Old ArtEngine
-    In the old C++ Art.Engine we had a global "fast" mode where caches were not invalidated under
-    any draw-matrix changes until fast-mode was turned off, then a final redraw pass was made
-    where pixel-inexact caches were invalidated and redrawn. This allowed good user interactivity
-    followed by maximum quality renders. This was handy for the more general-purpose Kimi-editor,
-    for the current purpose-built kimi-editor, it isn't needed.
+    cacheAt prop
+      We may add another option which lets of add a "cache-at" scale factor to force lower or
+      higher resolution caching.
+
+    global "fast-mode"
+      In the old C++ Art.Engine we had a global "fast" mode where caches were not invalidated under
+      any draw-matrix changes until fast-mode was turned off, then a final redraw pass was made
+      where pixel-inexact caches were invalidated and redrawn. This allowed good user interactivity
+      followed by maximum quality renders. This was handy for the more general-purpose Kimi-editor,
+      for the current purpose-built kimi-editor, it isn't needed.

   ###
+
+  _resetDrawCache: ->
+    @_drawCacheBitmap = null
+    @_drawCacheToElementMatrix = null
+
+  _drawPropertiesChanged: ->
+    @_clearDrawCache()
+
+  _elementToParentMatrixChanged: (oldElementToParentMatrix)->
+
+  _needsRedrawing: (descendant = @) ->
+    @_clearDrawCache()
+    if @getPendingVisible() && @getPendingOpacity() > 1/512
+      @getPendingParent()?._needsRedrawing descendant
+
+  # Whenever the drawCacheManager evicts a cache entry, it calls this
+  # on the appropriate element:
+  __clearDrawCacheCallbackFromDrawCacheManager: ->
+    @_resetDrawCache()
+
+  _clearDrawCache: ->
+    return unless @_drawCacheBitmap
+    drawCacheManager.doneWithCacheBitmap @
+    true
+
+  _releaseAllCacheBitmaps: ->
+    count = if @_clearDrawCache() then 1 else 0
+    count += child._releaseAllCacheBitmaps() for child in @_children
+    count

   @_cachingDraws: 0

@@ -927,7 +922,7 @@
     needsStagingBitmap: ->
       (@getHasChildren() || @getIsMask()) && (@_compositeMode != "normal" || @_opacity < 1 || @getChildRequiresParentStagingBitmap())

-      # override this for elements which are faster w/o caching (RectangleElement, BitmapElement)
+    # override this for elements which are faster w/o caching (RectangleElement, BitmapElement)
     cacheable: -> true

   _generateDrawCacheIfNeeded: ->


Update adminFormView.coffee

Update adminFormView.coffee

--- BackofficeBundle/Resources/public/coffee/adminFormView.coffee
+++ BackofficeBundle/Resources/public/coffee/adminFormView.coffee
@@ -21,4 +21,4 @@
       error: ->
         $('.modal-body', el).html 'Erreur durant le chargement'
     return
-)+)


add msg

add msg

--- atmospheric/002-accumulated-heat/csmain.coffee
+++ atmospheric/002-accumulated-heat/csmain.coffee
@@ -82,6 +82,7 @@

             viewer.paint(transformer.target(frame, (time / planet.period)), map, data)
             counter = (counter + 1) % 30
+            $("#msg").html('counter: ' + counter)
             time += tao
         handle = setInterval(evolve, 10000)


Replace one display hack with another

Replace one display hack with another

--- bokehjs/src/coffee/widget/handson_table.coffee
+++ bokehjs/src/coffee/widget/handson_table.coffee
@@ -13,7 +13,7 @@
       @render()
       @listenTo(@model, 'change:source', @render)

-    render: () ->
+    renderFn: () ->
       source = @mget("source")
       if source?
         headers = []
@@ -33,11 +33,18 @@
             if source == "edit"
               @editData(changes)
         })
-
-        ht = @$el.handsontable("getInstance")
-        ht.view.wt.draw(true) # XXX: hack to show table, but column sizes are still wrong (until window/node resize)
       else
         @$el.handsontable()
+
+    render: () ->
+      # XXX: we have to know when bokeh finished rendering to ... finish rendering
+      display = @$el.css("display")
+
+      interval = setInterval(() =>
+        if @$el.css("display") != display
+          clearInterval(interval)
+          @renderFn()
+      , 100)

     editData: (changes) ->
       source = @mget("source")


Point mobile hero units to MARTSY enum type in GraphQL

--- mobile/apps/home/routes.coffee
+++ mobile/apps/home/routes.coffee
@@ -3,7 +3,7 @@
 query = """
   query {
     home_page {
-      hero_units(platform: MOBILE) {
+      hero_units(platform: MARTSY) {
         mode
         heading
         title


Add singleton-related core extension plugin tests.

--- tests/js/core.plugin.coffee
+++ tests/js/core.plugin.coffee
@@ -93,5 +93,13 @@
     assert.strictEqual result.someOtherMixinProperty, 'bar',
       'Mixin property should have been set on deferred initialization.'

+  QUnit.test 'createPlugin with apiClass and baseMixins and asSingleton', (assert) ->
+    createOptions = @baseCreateOptions
+    createOptions.asSingleton = yes
+    hlf.createPlugin createOptions
+    result = assertGeneralPlugin.call @, assert
+    assert.strictEqual result, $('body').data('somePlugin'),
+      'Plugin singleton should be stored with context element.'
+

   yes


added default features

added default features

--- server-ce/settings.coffee
+++ server-ce/settings.coffee
@@ -295,7 +295,14 @@
 	references:{}
 	notifications:undefined

-
+	defaultFeatures:
+		collaborators: -1
+		dropbox: true
+		versioning: true
+		compileTimeout: 180
+		compileGroup: "standard"
+		references: true
+		templates: true

 #### OPTIONAL CONFIGERABLE SETTINGS


Removing wrong parameter from spec. Now it must return --format json

--- spec/rubocop-auto-correct-spec.coffee
+++ spec/rubocop-auto-correct-spec.coffee
@@ -100,7 +100,7 @@
   describe "when command with arguments", ->
     beforeEach ->
       buffer.setText("{ :atom => 'A hackable text editor for the 21st Century' }\n")
-      atom.config.set('rubocop-auto-correct.rubocopCommandPath', 'rubocop --format simple')
+      atom.config.set('rubocop-auto-correct.rubocopCommandPath', 'rubocop')

     describe "when correct buffer", ->
       it "manually run", ->


Fix parsing outcome for CF

Fix parsing outcome for CF

--- server/cron/downloadSubmits.coffee
+++ server/cron/downloadSubmits.coffee
@@ -228,7 +228,7 @@
             Подробнее
             ###
             re = new RegExp '[^<]*	\\s*([^<]*)	\\s*([^<]*)	\\s*([^<]*)	\\s*([^<]*)	\\s*([^]*?)	', 'gm'
-            outcomeRe = new RegExp '<option value="[^"]*" selected="selected"[^>]*>([^<]*)</option>', 'gm'
+            outcomeRe = new RegExp '<option value="[^"]*" selected="selected"[^>]*>([^<]*)<', 'gm'
             data = re.exec row
             if not data
                 logger.warn "Submit not found in line"


Add handful of events to the Segment.io whitelist


Change port range

Change port range

--- server/serversystem/manager.coffee
+++ server/serversystem/manager.coffee
@@ -21,7 +21,7 @@
 launchServer = (serv, lobby)->
   id = idCounter
   idCounter+=1
-  port = Math.floor(Math.random()*10000)+27000
+  port = Math.floor(Math.random()*1000)+27000
   serv.activeLobbies.push
     id: id
     port: port


changed default sorting on petitions dashboard

--- app/assets/javascripts/admin/petitions.js.coffee
+++ app/assets/javascripts/admin/petitions.js.coffee
@@ -6,4 +6,22 @@
     bServerSide: true
     sAjaxSource: $('#petitions').data('source')
     sDom: "<'row'<'span6'l><'span6'f>r>t<'row'<'span6'i><'span5'p>>"
-    aoColumnDefs: [{aTargets: [0], sCellType: "th"}]+    aoColumnDefs: [{aTargets: [0], sCellType: "th"}]
+    aoColumns: [
+      asSorting: [ "asc", "desc" ]
+    ,
+      asSorting: [ "desc", "asc" ]
+    ,
+      asSorting: [ "desc", "asc" ]
+    ,
+      asSorting: [ "desc", "asc" ]
+    ,
+      asSorting: [ "desc", "asc" ]
+    ,
+      asSorting: [ "desc", "asc" ]
+    ,
+      asSorting: [ "desc", "asc" ]
+    ,
+      asSorting: [ "desc", "asc" ]
+    ,
+      asSorting: [ "desc", "asc" ]]


Fix translations again argh

Fix translations again argh

--- app/assets/javascripts/application.js.erb.coffee
+++ app/assets/javascripts/application.js.erb.coffee
@@ -14,7 +14,7 @@
 #= require sweetalert
 #= require js.cookie
 #= require i18n
-#= require i18n/translations
+#= require_tree ./i18n
 #= require tinycolor-min
 #= require jquery.minicolors
 # local requires to be seen by everyone:


Oops...

Oops...

--- app/assets/javascripts/roofpig/Display.js.coffee
+++ app/assets/javascripts/roofpig/Display.js.coffee
@@ -33,7 +33,7 @@
     @scene = new THREE.Scene()
     @pieces3d = new Pieces3D(@scene, @settings)
     @camera = new Camera(@settings.hover)
-    @world3d = { pieces3d: @pieces3d, camera: @camera }
+    @world3d = { pieces: @pieces3d, camera: @camera }

     if (@settings.setup)
       setup_alg = new Alg(@settings.setup)


add js home view namespace

add js home view namespace

--- app/assets/javascripts/yoolk/namespace.js.coffee
+++ app/assets/javascripts/yoolk/namespace.js.coffee
@@ -16,6 +16,7 @@
 window.Yoolk.Views.Menu                 ||= {}
 window.Yoolk.Views.Menu.Categories      ||= {}
 window.Yoolk.Views.People               ||= {}
+window.Yoolk.Views.Home                 ||= {}

 # widgets
 window.Yoolk.Widgets                    ||= {}


route fix

route fix

--- js/routers/router.coffee
+++ js/routers/router.coffee
@@ -4,7 +4,7 @@
     Backbone.history.start()

   routes:
-    "": "destination"
+    "": "index"
     "destination": "destination"
     "route": "routeMap"
     "timeline": "timeline"


Added a bunch of (disabled) debug output. Fixed a bug about uniting touching hilites.

--- app/lib/dom_text_matcher/dom_text_hiliter.coffee
+++ app/lib/dom_text_matcher/dom_text_hiliter.coffee
@@ -80,13 +80,17 @@
         unless node?
           console.log "Node missing. Looking it up..."
           node = @domMapper.lookUpNode matches[0].element.pathInfo.path
-          console.log "Found. "
+#          console.log "Found. "
 #          console.log node
 #        else
 #          console.log "Node is:"
 #          console.log node
+#        console.log "Matches: "
+#        console.log matches
         # Calculate a normalized set of ranges
         ranges = @uniteRanges ({start: match.startCorrected, end: match.endCorrected, yields: match.yields } for match in matches)
+#        console.log "Ranges: "
+#        console.log ranges
         clone = node.cloneNode()
         match.element.pathInfo.node = clone for match in matches

@@ -108,7 +112,7 @@
             for range in ranges
               do (range) =>
                 if range.start is 0
-                  # This is the first range, and it starts at the start of the node
+                  # This is the first range, and it starts at the start of the node
                   nextPart = nextPart.splitText range.end
                   firstPart = nextPart.previousSibling
 #                  if firstPart.data isnt range.yields
@@ -126,7 +130,7 @@
                   lastPart = nextPart.splitText range.start - index
                   nextPart = null
                   remainingPart = lastPart.previousSibling
-#                  if lastPart.data isnt range.yields
+ #                 if lastPart.data isnt range.yields
 #                    console.log "End cut. Wanted: '" + range.yields + "'; got: '" + lastPart.data + "'."
 #                  else
 #                    console.log "Done end cut."
@@ -138,8 +142,10 @@
                   toRemove.push hl
                 else
                   # this range is is at the middle of the node
+#                  console.log "Gonna split @ " + range.start + "-" + index + " = " + (range.start - index) + " (len is: " + nextPart.data.length + ")"
                   middlePart = nextPart.splitText range.start - index
                   beforePart = middlePart.previousSibling
+#                  console.log "Gonna split @ " + range.end + "-" + range.start + "=" + (range.end - range.start) + " (len is: " + middlePart.data.length + ")"
                   nextPart = middlePart.splitText range.end - range.start
 #                  if middlePart.data isnt range.yields
 #                    console.log "Middle cut. Wanted: '" + range.yields + "'; got: '" + middlePart.data + "'."
@@ -223,9 +229,13 @@
   uniteRanges: (ranges) ->
     united = []
     delete lastRange
+#    console.log "Uniting ranges: "
+#    console.log ranges
     for range in ranges.sort @compareRanges
-      do (range) =>
+#        console.log "Doing range: "
+#        console.log range
         if lastRange? and lastRange.end >= range.start
+#          console.log "Can unite..."
           # there was a previous range, and we can continue it
           if range.end > lastRange.end
 #            console.log "Old yields: '" + lastRange.yields + "'."
@@ -236,10 +246,13 @@
             lastRange.end = range.end
 #            console.log "Should have appended the yield value, too: '" + lastRange.yields + "'."
         else
+#          console.log "New unit. (Last ended @ " + lastRange?.end + "; new start @ " + range.start + ")"
           # no previous range, or it's too far off
           united.push lastRange =
             start: range.start
             end: range.end
             yields: range.yields
+#    console.log "As united:"
+#    console.log united
     united


update stores

update stores

--- resources/assets/coffee/react/_components/comments-manager.coffee
+++ resources/assets/coffee/react/_components/comments-manager.coffee
@@ -15,6 +15,12 @@
 #    You should have received a copy of the GNU Affero General Public License
 #    along with osu!web.  If not, see <http://www.gnu.org/licenses/>.
 ###
+
+import core from 'osu-core-singleton'
+
+commentableMetaStore = core.dataStore.commentableMetaStore
+commentStore = core.dataStore.commentStore
+userStore = core.dataStore.userStore

 el = React.createElement

@@ -92,6 +98,10 @@

   update: (_event, {commentable_meta, comments, users}) =>
+    commentableMetaStore.initialize commentable_meta
+    commentStore.initialize comments
+    userStore.initialize users
+
     @setState
       comments: @mergeCollection @state.comments, comments
       users: @mergeCollection @state.users, users


Save the syntax validation setting in the onboarding view.

--- services/web/public/coffee/ide/FeatureOnboardingController.coffee
+++ services/web/public/coffee/ide/FeatureOnboardingController.coffee
@@ -7,11 +7,11 @@
 		$scope.innerStep = 1

 		$scope.turnCodeCheckOn = () ->
-			settings.saveProjectSettings({ syntaxValidation: true })
+			settings.saveSettings({ syntaxValidation: true })
 			navToInnerStep2()

 		$scope.turnCodeCheckOn = () ->
-			settings.saveProjectSettings({ syntaxValidation: false })
+			settings.saveSettings({ syntaxValidation: false })
 			navToInnerStep2()

 		$scope.dismiss = () ->


Fix automention: prepend url scheme if absent

--- assets/javascripts/views/new_post_form.js.coffee
+++ assets/javascripts/views/new_post_form.js.coffee
@@ -108,7 +108,9 @@
     delete data.mentions

     for entity in data.text.match(/\^(\S+)/g)
-      mentions.push { entity: entity.replace(/^\^/, '') }
+      entity = entity.replace(/^\^/, '')
+      entity = entity.replace(/^/, 'https://') unless entity.match(/^https?/)
+      mentions.push { entity: entity }

     data.mentions = mentions if mentions.length
     data


hub: fix a small issue involving a traceback when a cookie is messed up that I noticed in the logs.

--- salvus/hub.coffee
+++ salvus/hub.coffee
@@ -642,6 +642,9 @@
                     @remember_me_failed("no remember_me cookie")
                     return
                 x    = value.split('$')
+                if x.length != 4
+                    @remember_me_failed("invalid remember_me cookie")
+                    return
                 hash = generate_hash(x[0], x[1], x[2], x[3])
                 @remember_me_db.get
                     key         : hash


activity: define `MessageEventHelper::addInteraction` method

--- client/Social/Activity/messageeventhelper.coffee
+++ client/Social/Activity/messageeventhelper.coffee
@@ -5,6 +5,16 @@
     super options, data

     @messages = {}
+
+
+  addInteraction: (event) ->
+
+    {messageId, typeConstant} = event
+
+    return  unless message = @messages[messageId]
+
+    fn = @bound "add#{typeConstant.capitalize()}"
+    KD.utils.defer => fn message, event

   addLike: (message, {accountOldId, count}) ->


fix showing a team with no votes

fix showing a team with no votes

--- models/mongo.coffee
+++ models/mongo.coffee
@@ -103,12 +103,13 @@
           return fn error if error?
           cursor.toArray (error, array) ->
             return fn error if error?
-            teams = []
-            for team in array
-              Mongo.instantiate team, (error, unpacked) =>
+            return fn null, array if array.length == 0
+            items = []
+            for item in array
+              Mongo.instantiate item, (error, unpacked) =>
                 return fn error if error?
-                teams.push unpacked
-                fn null, teams  if teams.length == array.length
+                items.push unpacked
+                fn null, items if items.length == array.length

   InstanceMethods:
     collection: (fn) ->


app-updates: Changed update required modal title and content.

--- client/app/MainApp/kodingappscontroller.coffee
+++ client/app/MainApp/kodingappscontroller.coffee
@@ -626,12 +626,13 @@
     console.warn error.message, error

   showUpdateRequiredModal: (manifest) ->
-    modal = new KDModalView
-      title          : "App Update Available"
+    {name} = manifest
+    modal  = new KDModalView
+      title          : "Update Required for #{name}"
       content        : """

-
An update available for #{manifest.name}. You can update the app now or you can continue to use old version you have.

-
Warning: Updating the app will delete it's current folder to install new version. This cannot be undone. If you have updated files, back up them now.

+
Author of #{name} made this update required. You must update to keep on using the app.

+
Warning: Updating an app will delete it's current folder to install new version. This cannot be undone. If you have updated files, back up them now.

       """
       overlay        : yes
@@ -645,13 +646,9 @@
             @updateUserApp manifest, ->
               modal.buttons.Update.hideLoader()
               modal.destroy()
-        "Use This Version" :
-          style      : "modal-clean-gray"
-          callback   : =>
-            @skipUpdate = yes
-            @appManager.open manifest.name
-            modal.destroy()
-            @skipUpdate = no
+        "Close" :
+          style      : "modal-cancel"
+          callback   : => modal.destroy()

   defaultManifest = (type, name)->
     {profile} = KD.whoami()


app/toggle: fix typo and remove unnecessary props while rendering toggle

--- client/app/lib/components/common/toggle.coffee
+++ client/app/lib/components/common/toggle.coffee
@@ -1,15 +1,14 @@
 kd          = require 'kd'
 React       = require 'app/react'
-ReactToggle = require 'react-toggle'
+ReactToggle = require('react-toggle').default

 require './styl/toggle.styl'
 require './styl/popover.styl'

 module.exports = class Toggle extends React.Component

-  @defualtProps =
+  @defaultProps =
     size            : 'small'
-    defaultChecked  : no
     callback        : kd.noop

@@ -22,7 +21,7 @@

     className = "#{kd.utils.curry('ReactToggle', @props.className)} #{@props.size}"

-    props = _.omit @props, ['callback']
+    props = _.omit @props, ['callback', 'className', 'size']

     props.checked or= no
     <div className={className}>


ComputeHelpers: require remote when needed

--- client/app/lib/providers/computehelpers.coffee
+++ client/app/lib/providers/computehelpers.coffee
@@ -6,7 +6,6 @@
 ComputePlansModalPaid = require './computeplansmodalpaid'
 ComputePlansModalFree = require './computeplansmodalfree'
 KDNotificationView = kd.NotificationView
-remote = require('./remote').getInstance()

 module.exports = class ComputeHelpers
@@ -129,7 +128,8 @@

     provisioner = machine.provisioners?.first
     return callback null  unless provisioner
-
+
+    remote = require('app/remote').getInstance()
     remote.api.JProvisioner.one slug: provisioner, callback


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/metered.coffee
+++ test/metered.coffee
@@ -7,5 +7,6 @@
   ok obj.meter

 test "should set max<Attribute> if one doesn't exist", ->
+  obj =

 module()


Value in fields should appear like it does in grid

--- client/lanes/components/grid/EditingMixin.cjsx
+++ client/lanes/components/grid/EditingMixin.cjsx
@@ -39,7 +39,10 @@
         </div>

     getFieldValue: (column) ->
-        @props.model[column.id]
+        if column.format
+            column.format(@props.model[column.id], @props.model)
+        else
+            @props.model[column.id]

     onFieldChange: (column, ev) ->
         @props.model[column.id] = ev.target.value
@@ -64,6 +67,7 @@
             @props.editors[column.id](model: @props.model)
         else
             <input type="text"
+                style={textAlign: column.textAlign}
                 value={@getFieldValue(column)}
                 onChange={_.partial(@onFieldChange, column)} />
         <div key={column.id} style={flex: column.flex} className="field">


trash

trash


Added Model testers `equalsci` and `tests`

--- lib/ST/Model/Base.coffee
+++ lib/ST/Model/Base.coffee
@@ -272,12 +272,20 @@
             not:        _not
           }
         equals: (value) ->
-          value = String value
+          value = String(value)
           {
             type:       'equals'
             attribute:  name
             value:      value
             test:       (item) -> String(item[name]()) == value
+            not:        _not
+          }
+        equalsci: (value) ->
+          value = String(value).toLowerCase()
+          {
+            attribute:  name
+            value:      value
+            test:       (item) -> String(item[name]()).toLowerCase() == value
             not:        _not
           }
         lessThan: (value) ->
@@ -310,6 +318,12 @@
             attribute:  name
             value:      value
             test:       (item) -> Number(item[name]()) >= value
+            not:        _not
+          }
+        tests: (callback) ->
+          {
+            attribute:  name
+            test:       callback
             not:        _not
           }
       }


refactoring the main test suite

refactoring the main test suite

--- test/test_cs.coffee
+++ test/test_cs.coffee
@@ -191,18 +191,36 @@
       c, d]
 """

-src.object = """
+src.object1 = """
 obj = { a:1; b:{c:1} }
 """

-src.indented_object = """
+src.object2 = """
 obj = {
     a:1
     b:2,
-      c:3
+    c:3
     d:{e:4; f:5}
 }
 """
+
+ast.object2 = [
+    tree 'Op', '=', (tree 'Id', 'obj'),
+        tree 'Object', [
+            [ 'a', (tree 'Number', 1) ]
+            [ 'b', (tree 'Number', 2) ]
+            [ 'c', (tree 'Number', 3) ]
+            [ 'd',  tree 'Object', [
+                [ 'e', (tree 'Number', 4) ]
+                [ 'f', (tree 'Number', 5) ]]]]]
+
+src.object3 = '''
+{
+    a: {
+        b: {
+            c:1
+        d:2 } } }
+'''

 src.operators = """
     a + b * c
@@ -266,54 +284,78 @@
             (tree 'Id', 'y')),
         (tree 'Id', 'z')) ]

-if process.argv.length>0
-    used_regex = { }
-    listed_tests = { }
-    for name of src
-        for regex in process.argv
-            if name.match regex
-                listed_tests[name] = true
-                used_regex[regex]  = true
-                break
-    for regex in process.argv
-        unless used_regex[regex]
-            print "Warning, unused regex '#{regex}'\n"
-
-log = [ ]
-logError = (msg)->
-    print "\n>>>>>>>>> ERROR: #{msg}\n"
-    log.push msg
-
-# Pass all tests in sequence
-for name, x of src
-    continue if listed_tests and not listed_tests[name]
+
+# Determine the set of test names to run
+# src: test suite
+# regexes: list of regexes, presumably passed from command line
+getTestsToRun = (src, regexes) ->
+    tests = { }
+    if regexes.length is 0
+        (tests[name] = true) for name of src
+    else
+        used_regexes = { }
+        for name of src
+            for regex in regexes
+                if name.match regex
+                    tests[name] = true
+                    used_regexes[regex]  = true
+                    break
+        for regex in regexes
+            unless used_regex[regex]
+                print "Warning, unused regex '#{regex}'\n"
+    return tests
+
+# Run one test.
+# name: name of the test. The test must fail iff the name starts with "fail_".
+# src:  source string of the test.
+# ast:  AST that must be produced as a result (optional).
+# logger: what to do if a test fails.
+runTest = (name, src, ast, logger) ->
+    logger ?= print
     print "\n***** Test #{name} *****\n"
-    t = cs.parse x
+    t = cs.parse src
     hasFailed  = t is gg.fail
     shouldFail = name.match /^fail_/
-    if (a=ast[name])
-        astFailed = not astEqual a, t
+    if ast?
+        astFailed = not astEqual ast, t
         hasFailed ||= astFailed
         #if astEqual(a,t) != (a.toString()==t.toString())
         #    print "\nEquality test failed for #{a} and #{t}!!!\n"
     else astFailed = false

     if hasFailed and shouldFail
-        print "\n#{name} input:\n#{x}\n\nCompilation of #{name} failed, as expected\n"
+        print "\n#{name} input:\n#{src}\n\nCompilation of #{name} failed, as expected\n"
+        return true
     else if astFailed and not shouldFail
-        logError "Failure, invalid tree for src.#{name}\nexp: #{ast[name]}\ngot: #{t}"
+        logger "Failure, invalid tree for src.#{name}\nexp: #{ast}\ngot: #{t}"
+        return false
     else if hasFailed and not shouldFail
-        logError "Failure, cannot parse src.#{name}"
+        logger "Failure, cannot parse src.#{name}"
+        return false
     else if not hasFailed and shouldFail
-        logError "Test #{name} should have failed"
+        logger "Test #{name} should have failed"
+        return false
     else if not hasFailed and not shouldFail
-        print "\n#{name} input:\n#{x}\n\n#{name} result:\n#{toIndentedString t}\n"
-
-if log.length==0
-    print "\nAll tests passed successfully\n"
-else
-    n=0; (n++ for _ of src)
-    print "\n#{log.length} failures out of #{n}:\n\n#{log.join '\n\n'}\n"
+        print "\n#{name} input:\n#{src}\n\n#{name} result:\n#{toIndentedString t}\n"
+        return true
+
+main = (args) ->
+
+    log = [ ]
+    logger = (msg) ->
+        print "\n>>>>>>>>> ERROR: #{msg}\n"
+        log.push msg
+
+    total = success = 0
+    for name of getTestsToRun(src, args)
+        total++
+        success++ if runTest name, src[name], ast[name], logger
+    if total == success
+        print "\nAll #{total} tests passed successfully\n"
+    else
+        print "\n#{total-success} failures out of #{total}:\n\n#{log.join '\n\n'}\n"
+
+main (process.argv)

 #TODO: src finishing with a comment


:art: in BufferSearch

:art: in BufferSearch

--- lib/buffer-search.coffee
+++ lib/buffer-search.coffee
@@ -116,21 +116,25 @@

     changes = @patch.changes()
     change = null
-    changesDone = false
-    changeEnd = Point.ZERO
+    withinChange = false
     scanEnd = Point.ZERO
-    withinChange = false
     markerIndex = 0
-    lastValidMarkerIndex = -1
+    markerIndexBeforeChange = -1

     while (marker = @markers[markerIndex])? or change?
-      unless change?
-        until changesDone or changeEnd?.isGreaterThan(scanEnd)
-          {value: change, done: changesDone} = changes.next()
-          changeEnd = change.position.traverse(change.newExtent) if change?
-
       markerRange = marker?.getBufferRange()

+      # If we have already re-scanned the region affected by this change, find
+      # the next change that extends beyond what we've scanned.
+      until change? or (next = changes.next()).done
+        change = next.value
+        changeStart = change.position
+        changeEnd = changeStart.traverse(change.newExtent)
+        break if changeEnd.isGreaterThanOrEqual(scanEnd)
+        change = null
+
+      # If this marker is untouched and follows the change, or there are no more
+      # markers, then it is time to re-scan.
       if withinChange
         if marker?
           if marker.isValid() and markerRange.start.isGreaterThan(changeEnd)
@@ -141,9 +145,9 @@
           scanEnd = Point.INFINITY

         if not withinChange
-          if lastValidMarkerIndex >= 0
-            scanStart = @markers[lastValidMarkerIndex].getBufferRange().start
-            spliceIndex = lastValidMarkerIndex
+          if markerIndexBeforeChange >= 0
+            scanStart = @markers[markerIndexBeforeChange].getBufferRange().start
+            spliceIndex = markerIndexBeforeChange
           else
             scanStart = Point.ZERO
             spliceIndex = 0
@@ -152,16 +156,18 @@
           oldMarkers = @markers.splice(spliceIndex, markerIndex - spliceIndex + 1, newMarkers...)
           oldMarker.destroy() for oldMarker in oldMarkers
           markerIndex += newMarkers.length - oldMarkers.length
-          lastValidMarkerIndex = markerIndex
+          markerIndexBeforeChange = markerIndex
           change = null
+
+      else if change? and markerRange.end.isGreaterThanOrEqual(changeStart)
+        withinChange = true
+      else if marker.isValid()
+        markerIndexBeforeChange = markerIndex
+
+      # A marker that is invalid but is outside of any change can simply be
+      # recreated. It must have been temporarily changed but then restored.
       else
-        if change? and markerRange.end.isGreaterThanOrEqual(change.position)
-          withinChange = true
-        else
-          if marker.isValid()
-            lastValidMarkerIndex = markerIndex
-          else
-            @markers[markerIndex] = @recreateMarker(marker)
+        @markers[markerIndex] = @recreateMarker(marker)

       markerIndex++


fix ebook-convert path error

fix ebook-convert path error

--- lib/ebook-convert.coffee
+++ lib/ebook-convert.coffee
@@ -1,5 +1,6 @@
 path = require 'path'
 {execFile} = require 'child_process'
+{Directory} = require 'atom'
 # ebook-convert is requied (calibre), which can be got from https://calibre-ebook.com/download
 # xpath http://www.w3schools.com/xsl/xpath_syntax.asp

@@ -76,9 +77,13 @@
     args.push '--margin-left'
     args.push marginLeft

-  execFile 'ebook-convert',
-            args,
-            callback
+  # ebook-convert will cause error if directory doesn't exist,
+  # therefore I will create directory first.
+  directory = new Directory(path.dirname(dest))
+  directory.create().then (flag)->
+    execFile 'ebook-convert',
+              args,
+              callback

 module.exports = ebookConvert


:new: Trigger LinterRegistry::lint from EditorLinter::lint

--- lib/editor-linter.coffee
+++ lib/editor-linter.coffee
@@ -20,9 +20,8 @@
           cursorImmediate = setImmediate => @emitter.emit('should-update-line-messages')
         @emitter.emit('should-update-bubble')

-  lint: ->
-    # Does nothing for now, I'll work again after a PR porting linter-registry from NG branch
-    console.log("I should lint something")
+  lint: (onChange = false) ->
+    @emitter.emit('should-lint', onChange)

   onShouldUpdateBubble: (callback) ->
     return @emitter.on('should-update-bubble', callback)


Rename decoration type 'gutter' -> 'line-number'

--- lib/git-diff-view.coffee
+++ lib/git-diff-view.coffee
@@ -113,5 +113,5 @@

   markRange: (startRow, endRow, klass) ->
     marker = @editor.markBufferRange([[startRow, 0], [endRow, Infinity]], invalidate: 'never')
-    @editor.decorateMarker(marker, type: 'gutter', class: klass)
+    @editor.decorateMarker(marker, type: 'line-number', class: klass)
     @markers.push(marker)


cistrans: When highlighting a peak, all peaks for that probe are highlighted

--- cistrans/cistrans.coffee
+++ cistrans/cistrans.coffee
@@ -42,7 +42,7 @@
   lightGray = d3.rgb(230, 230, 230)
   darkGray = d3.rgb(200, 200, 200)
   darkblue = "darkslateblue"
-  darkgreen = "darkslateblue" # <- was highlighting eqtl peaks in different colors, but I didn't like it
+  darkgreen = "darkgreen"
   pink = "hotpink" # "#E9CFEC"
   purple = "#8C4374"
   # bgcolor = "black"
@@ -333,30 +333,25 @@
              .data(data.peaks)
              .enter()
              .append("circle")
+             .attr("class", (d) -> "probe#{d.probe}")
              .attr("cx", (d) -> chrXScale[d.chr](d.pos_cM))
              .attr("cy", (d) -> chrYScale[data.probes[d.probe].chr](data.probes[d.probe].pos_cM))
              .attr("r", peakRad)
              .attr("stroke", "none")
-             .attr("fill", (d) ->
-                 if (chrindex[d.chr] + chrindex[data.probes[d.probe].chr]) % 2 == 0
-                   return darkblue
-                 else
-                   return darkgreen)
+             .attr("fill", (d) -> return if(chrindex[d.chr] % 2 == 0) then darkblue else darkgreen)
              .attr("opacity", (d) -> Zscale(d.lod))
              .on "mouseover", (d) ->
-                 d3.select(this).attr("r", bigRad)
+                 d3.selectAll("circle.probe#{d.probe}")
+                                .attr("r", bigRad)
                                 .attr("fill", pink)
                                 .attr("stroke", darkblue)
                                 .attr("stroke-width", 1)
                                 .attr("opacity", 1)
                  eqtltip.call(this,d)
              .on "mouseout", (d) ->
-                 d3.select(this).attr("r", peakRad)
-                                .attr("fill", (d) ->
-                                    if (chrindex[d.chr] + chrindex[data.probes[d.probe].chr]) % 2 == 0
-                                      return darkblue
-                                    else
-                                      return darkgreen)
+                 d3.selectAll("circle.probe#{d.probe}")
+                                .attr("r", peakRad)
+                                .attr("fill", (d) -> return if(chrindex[d.chr] % 2 == 0) then darkblue else darkgreen)
                                 .attr("stroke", "none")
                                 .attr("opacity", (d) -> Zscale(d.lod))
                  d3.selectAll("#eqtltip").remove()


Home: Add widget HTML, CSS and JS codes to page.

--- client/Intro/home.coffee
+++ client/Intro/home.coffee
@@ -1,6 +1,8 @@
 class HomePage extends JView

   iframe = """"""
+
+  partialData = KD.customPartial?.partial or {}

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

@@ -48,27 +50,34 @@
             "width=#{w},height=#{h},left=#{Math.floor (screen.width/2) - (w/2)},top=#{Math.floor (screen.height/2) - (h/2)}"

     @markers = new MarkerController
-
+    @widgetPlaceholder = new KDCustomHTMLView
+      cssClass         : "home-widget-placeholder"
+
+    if partialData.css
+      tag           = document.createElement "style"
+      tag.innerHTML = partialData.css
+
+      document.head.appendChild tag
+
+    if partialData.js
+      try
+        eval Encoder.htmlDecode partialData.js
+      catch
+        console.warn "Home page custom code failed to execute"

   showVideo:->
-
     @play.hide()
     @markers.hide 'teamwork'
     @$('figure.laptop section.teamwork').hide()
     @$('figure.laptop').append iframe

-
-
   hideVideo:->
-
     @play.show()
     @$('figure.laptop section.teamwork').show()
     @$('figure.laptop iframe').remove()
     KD.utils.wait 1000, => @markers.show 'teamwork'

-
   show:->
-
     @appendToDomBody()  unless document.getElementById 'home-page'

     @unsetClass 'out'
@@ -78,9 +87,7 @@

     KD.utils.defer => @markers.reset()

-
   hide:->
-
     @setClass 'out'
     document.body.classList.remove 'intro'

@@ -141,8 +148,8 @@
     @markers.group 'teamwork', vms, nav, chat, play

   pistachio:->
-    if KD.customPartial?.partial
-      return Encoder.htmlDecode KD.customPartial.partial.html
+    if partialData.html
+      return Encoder.htmlDecode partialData.html

     """

@@ -152,6 +159,7 @@
           LOGIN

+      {{> @widgetPlaceholder}}