bye bye grunt

bye bye grunt

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,205 +0,0 @@
-module.exports = (grunt) ->
-
-
-  # paths setup - separate as some modules dont process templates correctly
-  paths =
-
-    # build directory
-    build_dir: 'build'
-
-    # -- sources --
-
-    # coffescript sources
-    coffee_dir: 'coffee'
-    coffee_src: 'coffee/**/*.coffee'
-
-    # less sources
-    less_dir: 'less'
-    less_src: 'less/**/*.less'
-
-    # -- compiled output --
-
-    # javascript sources
-    js_dir: 'build/js'
-    js_src: 'build/js/src/**/*.js'
-    js_specs: 'build/js/test/**/*.spec.js'
-
-    # css sources
-    css_dir: 'build/css'
-    css_src: 'build/css/**/*.css'
-    css_dest: 'build/css/acorn.player.css'
-
-    # minified target name
-    minified: 'build/acorn.player.min.js'
-
-    # jasmine test page to render
-    test_page: 'static/tests.html'
-
-    # libraries to load in the frontend
-    frontend_libs: [
-      'lib/closure/library/closure/goog/base.js'     # for dependencies
-      'lib/jquery/jquery.min.js'                     # for dom manipulation
-      'node_modules/underscore/underscore.js'        # for utilities
-      'node_modules/backbone/backbone.js'            # for mvc apps
-      'node_modules/marked/lib/marked.js'            # for docs
-      'lib/bootstrap/bootstrap.noicons.min.js'       # for widgets
-      'lib/athena.lib.min.js'                        # for athena utils
-      'lib/jquery/jquery.object-fit.js'              # for object-fit
-      'lib/jquery/jquery.fullscreen.js'
-    ]
-
-
-  # YOU SHOULD NOT NEED TO MODIFY BELOW THIS LINE.
-  # you may have to... if things break...
-
-
-  # google closure paths
-  paths.closure =
-
-    # dependencies file
-    deps: "#{paths.js_dir}/deps.js"
-
-    # main entry point
-    main: "#{paths.js_dir}/src/main.js"
-
-    # output file for the compiler
-    compiled: paths.minified
-
-    # root of the sources that closure should use
-    # silliness. because depswriter.py uses paths relative to closure library
-    root_with_prefix: "'#{paths.js_dir} ../../../../../#{paths.js_dir}'"
-
-    # path to library. this should be a submodule.
-    library: 'lib/closure/library'
-
-    # path to compiler. this should be a symlink (or the actual jar).
-    compiler: 'lib/closure/compiler.jar'
-
-
-  # jasmine paths
-  paths.jasmine =
-
-    # lib to include before sources (e.g. jquery, underscore, etc).
-    lib: paths.frontend_libs
-
-    # src to include. use closure deps and main entry point
-    src: [paths.closure.deps, paths.closure.main]
-
-    # specs to include.
-    specs: paths.js_specs
-
-    # view to render
-    view: paths.test_page
-
-
-  # Project configuration.
-  grunt.initConfig
-
-    # load package information
-    pkg: grunt.file.readJSON 'package.json'
-
-    # task to compile coffeescript into javascript
-    coffee:
-      default:
-        src: paths.coffee_src
-        dest: paths.js_dir
-        options:
-          preserve_dirs: true
-          base_path: paths.coffee_dir
-
-    # task to compute file dependencies (closure)
-    closureDepsWriter:
-      default:
-        closureLibraryPath: paths.closure.library
-        options:
-          output_file: paths.closure.deps
-          root_with_prefix: paths.closure.root_with_prefix
-
-    # task to compile code into a minified file (closure)
-    closureCompiler:
-      default:
-        js: paths.js_src
-        closureCompiler: paths.closure.compiler
-        checkModified: true
-        options:
-           # compilation_level: 'ADVANCED_OPTIMIZATIONS',
-           # externs: ['path/to/file.js', '/source/**/*.js'],
-           # define: ["'goog.DEBUG=false'"],
-           # warning_level: 'verbose',
-           # jscomp_off: ['checkTypes', 'fileoverviewTags'],
-           # summary_detail_level: 3,
-           formatting: 'PRETTY_PRINT'
-           js_output_file: paths.closure.compiled
-           output_wrapper: '"\'use strict\';\n(function(){%output%}).call(this);"'
-
-    # task to run jasmine tests through the commandline via phantomjs
-    jasmine:
-      # concat because jasmine-runner doesnt support libs (before srcs)
-      src: [].concat(paths.jasmine.lib, paths.jasmine.src)
-      specs: paths.jasmine.specs
-
-    # task to run jasmine tests in a webserver
-    jasmineSpecServer:
-      lib: paths.jasmine.lib
-      src: paths.jasmine.src
-      specs: paths.jasmine.specs
-      view: paths.jasmine.view
-      port: 8090
-
-    # task to watch sources for changes and recompile during development
-    watch:
-      compile:
-        files: paths.coffee_src
-        tasks: 'compile'
-      specs:
-        files: [paths.coffee_src, paths.less_src]
-        tasks: 'deps'
-      less:
-        files: paths.less_src
-        tasks: 'less:dev'
-
-    # task to run shell commands
-    exec:
-      # create the build directory. closure errors out if it isn't there...
-      mkbuild: command: "mkdir -p #{paths.build_dir}"
-
-
-    # task to clean up directories
-    clean:
-
-      # the generated build dir
-      build: paths.build_dir
-
-      # the generated jasmine-runner tester file
-      test: ['_SpecRunner.html']
-
-    # task to compile .less stylesheets to .css
-    less:
-      # .less -> .css compilation and minification
-      prod:
-        src: paths.less_src
-        dest: paths.css_dest
-        options:
-          yuicompress: true
-      # .less -> .css compilation only
-      dev:
-        src: paths.less_src
-        dest: paths.css_dest
-
-
-
-  # Load tasks
-  grunt.loadNpmTasks 'grunt-exec'
-  grunt.loadNpmTasks 'grunt-coffee'
-  grunt.loadNpmTasks 'grunt-contrib-clean'
-  grunt.loadNpmTasks 'grunt-contrib-less'
-  grunt.loadNpmTasks 'grunt-closure-tools'
-  grunt.loadNpmTasks 'grunt-jasmine-runner'
-  grunt.loadNpmTasks 'grunt-jasmine-spec-server'
-
-  # Register tasks
-  grunt.registerTask 'compile', ['exec:mkbuild', 'deps', 'closureCompiler']
-  grunt.registerTask 'deps', ['coffee', 'less:dev', 'closureDepsWriter']
-  grunt.registerTask 'test', ['clean', 'deps', 'jasmine', 'clean:test']
-  grunt.registerTask 'server', ['deps', 'jasmineSpecServer', 'watch:specs']
-  grunt.registerTask 'default', ['compile']


Fix indentation.

Fix indentation.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -141,13 +141,13 @@
           template: 'node_modules/ink-docstrap/template'
           configure: 'jsdoc.conf.json'
     'gh-pages':
-        options:
-          base: 'doc'
-          message: 'Update documentation'
-          user:
-            name: 'Travis CI Server'
-            email: 'alex.scown@hp.com'
-        src: '**/*'
+      src: '**/*'
+      options:
+        base: 'doc'
+        message: 'Update documentation'
+        user:
+          name: 'Travis CI Server'
+        email: 'alex.scown@hp.com'

   grunt.loadNpmTasks 'grunt-contrib-clean'
   grunt.loadNpmTasks 'grunt-contrib-connect'


more release utils in grunt

more release utils in grunt

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -88,20 +88,20 @@
     type = type || 'patch'
     grunt.task.run 'bumpup:' + type

-  grunt.registerTask 'release:git', [
-    'git:add:package.json', 'git:commit:release', 'git:tag:release'
-  ]
+  grunt.registerTask 'release:commit', [ 'git:add:package.json', 'git:commit:release', 'git:tag:release' ]

-  grunt.registerTask 'release', [ 'release:prep', 'release:bump', 'release:git' ]
+  grunt.registerTask 'release:patch', [ 'release:prep', 'release:bump:patch', 'release:commit' ]
+
+  grunt.registerTask 'release:push', ['git:push', 'git:pushTags' ]
+  grunt.registerTask 'release:publish', ['release:push', 'npm:publish' ]

   grunt.registerTask 'git:add', (file) ->
     res = runSimple "git add #{file}"

-
   grunt.registerTask 'git:commit:release', ->
-    msg = grunt.config release.options.commitMessage
-    return runSimple "git commit -m #{msg}"
+    msg = grunt.config 'release.options.commitMessage'
+    return runSimple "git commit -m '#{msg}'"

   grunt.registerTask 'git:isClean', ->
     res = runSilent 'git status -s'
@@ -110,16 +110,26 @@
     grunt.log.writelns res.output
     return false

+  grunt.registerTask 'git:push', ->
+    return runSimple 'git push'
+
+  grunt.registerTask 'git:pushTags', ->
+    return runSimple 'git push --tags'
+
   grunt.registerTask 'git:tag:release', ->
-    tag = grunt.config release.options.tagName
-    msg = grunt.config.release.options.tagMessage
-    return runSimple "git tag  #{tag} -m #{msg}"
+    tag = grunt.config 'release.options.tagName'
+    msg = grunt.config 'release.options.tagMessage'
+    return runSimple "git tag  #{tag} -m '#{msg}'"

-  _semverSync = 'node_modules/semver-sync/bin/semver-sync'
+
+  grunt.registerTask 'npm:publish', (tag) ->
+    tag = ' -- tag %s' % [tag] if tag
+    return runSimple "npm publish #{tag}"
+

   grunt.registerTask 'semver:isSynced', ->
-    res = runSilent "#{_semverSync} --verify"
-    #return false unless res.code == 0
+    res = runSilent "node_modules/.bin/semver-sync --verify"
+    return false unless res.code == 0
     if res.code == 0
       grunt.log.writeln res.output
       return
@@ -127,5 +137,5 @@
     return false

-  # release:
-  # bump, stage+commit+tag+push, publish
+# release:
+# bump, stage+commit+tag+push, publish


Enable LiveReload in Gruntfile.

Enable LiveReload in Gruntfile.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -49,6 +49,8 @@
 				files: '<%= concat.main.src %>'
 				tasks: 'concat'
 			stylus:
+				options:
+					livereload: true
 				files: [
 					'tamia/**/*.styl',
 					'specs/specs.styl'


Commented out test task since it caused an infinite loop

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -65,11 +65,11 @@
         tasks: ['coffee:server']
         options:
           atBegin: true
-      serverTests:
-        files: ['build/server/**/*.js', 'spec/server/**/*.js']
-        tasks: ['mochaTest:test']
-        options:
-          spawn: false
+#      serverTests:
+#        files: ['build/server/**/*.js', 'spec/server/**/*.js']
+#        tasks: ['mochaTest:test']
+#        options:
+#          spawn: false
     concurrent:
       target:
         tasks: ['nodemon', 'watch']
@@ -124,3 +124,5 @@

     if filepath.match 'spec/server'
       grunt.config 'mockaTest.test.src', 'filepath'
+
+  grunt.registerTask 'default', ['shell:mongo', 'concurrent:target']


MISC: display stdout for grunt push_demo task.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -12,6 +12,8 @@

       push_ghpages:
         command: 'git subtree push --prefix demo origin gh-pages'
+        options:
+          stdout: true

     jasmine_test:
       all: ['lib/spec/']


Removed karma because its broken.

Removed karma because its broken.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -198,7 +198,7 @@
       # Changes to app code should be validated and re-copied to the `build`, triggering `watch:build`
       unit:
         files:      '<%= CLIENT_DIR + JS_FILES %>'
-        tasks:      [ 'copy:js', 'jshint', 'karma:background:run' ]
+        tasks:      [ 'copy:js', 'jshint' ]

   # Dependencies


renamed wrapped_files task to wrap

renamed wrapped_files task to wrap

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -21,7 +21,7 @@
           }
         ]
     concat:
-      wrapped_files:
+      wrap:
         src: [
           'wrap/first.js',
           'plugins/*.js',


Watch for changes in server files and auto-restart server

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -32,6 +32,7 @@
           livereload: true
           nospawn: true
         files: [
+          "src/app/**/*.coffee"
           "src/coffee/**/*.coffee"
           "src/test/coffee/**/*.coffee"
         ]


Reverse last commit.

Reverse last commit.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -22,7 +22,7 @@
     mochacov:
       options:
         compilers: ['coffee:coffee-script/register']
-        files: ['test/expander-test.coffee', 'test/glob-html-test.coffee', 'test/processor-test.coffee']
+        files: ['test/*.coffee']
       coverage:
         options:
           coveralls: true


Added prepare executable

Added prepare executable

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -34,3 +34,13 @@
   Grunt.initConfig
     coffee: coffeeConfiguration
     clean: cleanConfiguration
+
+  # Add executable line to top of file
+  Grunt.registerTask 'prepare-executable', () ->
+    exeLoc = __dirname + '/bin/Commander.js'
+    data = fs.readFileSync(exeLoc)
+    fd = fs.openSync exeLoc, 'w+'
+    buffer = new Buffer '#!/usr/bin/env node\n\n'
+    fs.writeSync(fd, buffer, 0, buffer.length)
+    fs.writeSync(fd, data, 0, data.length)
+    fs.close(fd)


change testing framework to Mocha

change testing framework to Mocha

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -113,10 +113,6 @@
         files: [ 'tests/qunit/*/*.html' ]

       mocha:
-        all: 'tests/mocha/*.html'
-        core: 'test/mocha/enchant.js/*.html'
-
-      mocha:
         test:
           src: ['tests/mocha/**/*.html']
           options:
@@ -146,4 +142,4 @@
   grunt.loadTasks 'tasks/'

   # Default task.
-  grunt.registerTask 'default', [ 'jshint:core', 'concat', 'uglify', 'qunit', 'lang:en', 'lang:ja', 'lang:de', 'build' ]
+  grunt.registerTask 'default', [ 'jshint:core', 'concat', 'uglify', 'mocha', 'lang:en', 'lang:ja', 'lang:de', 'build' ]


added syntaxhighlighter to the concat task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -34,6 +34,13 @@
                     'assets/dependencies/bootstrap/dist/js/bootstrap.min.js',
                 ]
                 dest: 'assets/compiled/libs.js'
+            syntax:
+                src: [
+                    'assets/dependencies/SyntaxHighlighter/scripts/XRegExp.js',
+                    'assets/dependencies/SyntaxHighlighter/scripts/shCore.js',
+                    'assets/dependencies/SyntaxHighlighter/scripts/shBrushJScript.js'
+                ]
+                dest: 'assets/compiled/syntax.js'
         notify_hooks:
             options:
                 enabled: true


Fix concat line in build.

Fix concat line in build.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -18,7 +18,7 @@
         concat:
             dist:
                 files:
-                    'dist/angular-gettext.js': ['src/index.js', 'dist/angular-gettext.js', 'src/*.js']
+                    'dist/angular-gettext.js': ['src/index.js', 'src/*.js']

         uglify:
             dist:


Lint the specs

Lint the specs

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -37,12 +37,13 @@
         options:
           output: 'spec/result.xml'
           reporter: 'spec'
+          urls: ['http://localhost:8000/spec/runner.html']
           failWithOutput: true
-          urls: ['http://localhost:8000/spec/runner.html']

     # Coding standards
     coffeelint:
       components: ['components/*.coffee']
+      spec: ['spec/*.coffee']

   # Grunt plugins used for building
   @loadNpmTasks 'grunt-contrib-coffee'


fix jasmine libs path error

fix jasmine libs path error

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -68,7 +68,7 @@
           styles: 'dist/css/<%= pkg.name %>.css',
           specs: 'spec/build/javascripts/*.spec.js',
           vendor: [
-            'bower_components/jQuery/dist/jquery.js',
+            'bower_components/jquery/dist/jquery.js',
             'bower_components/Caret.js/src/*.js'
           ],
           helpers: [


Include new zendesk script in dist build

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -162,6 +162,8 @@
               'cs!helpers/backbone/views/editable'

               'cs!configs/aloha'
+
+              'modules/header/zendesk'
             ]
             exclude: ['coffee-script', 'less/normalize']
             excludeShallow: ['settings']


grunt needs dev config too

grunt needs dev config too

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -25,3 +25,4 @@
   grunt.registerTask "build", ["coffee"]
   grunt.registerTask "deploy", ["copy", "build", "clean"]
   grunt.registerTask 'heroku:production', 'build'
+  grunt.registerTask 'heroku:development', 'build'


Use TRAVIS_COMMIT environment variable to detect commit id when available

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,46 +1,3 @@
-browserstack = (grunt) ->
-  log = (message) -> grunt.log.write(message)
-
-  exec = require('child_process').exec
-  ->
-    new Promise (resolve, reject) ->
-      child = exec './node_modules/.bin/browserstack-runner', (error, stdout, stderr) ->
-        if error
-          reject(error)
-        else
-          resolve(stdout.toString())
-      child.stdout.on('data', log)
-      child.stderr.on('data', log)
-
-setCommitStatus = (sha, state) ->
-  GitHub = require('github')
-  github = new GitHub
-    version: '3.0.0'
-    protocol: 'https',
-    timeout: 5000,
-    headers:
-      'user-agent': 'github-status-reporter'
-
-  github.authenticate
-    type: 'oauth'
-    token: process.env.GITHUB_TOKEN
-
-  repo = (process.env.TRAVIS_REPO_SLUG || 'baconjs/bacon.js').split('/', 2)
-
-  q =
-    user: repo[0],
-    repo: repo[1],
-    sha: sha,
-    state: state
-    context: 'continous-integration/browserstack'
-
-  new Promise (resolve, reject) ->
-    github.statuses.create q, (error) ->
-      if error
-        reject(error)
-      else
-        resolve()
-
 module.exports = (grunt) ->
   grunt.initConfig
     clean:
@@ -86,9 +43,57 @@
     fs.writeFileSync('README.md', readmegen readmedoc)

   grunt.registerTask 'browserstack-status', 'Run BrowserStack tests and report status to GitHub', ->
+    browserstack = (grunt) ->
+      log = (message) -> grunt.log.write(message)
+
+      exec = require('child_process').exec
+      ->
+        new Promise (resolve, reject) ->
+          child = exec './node_modules/.bin/browserstack-runner', (error, stdout, stderr) ->
+            if error
+              reject(error)
+            else
+              resolve(stdout.toString())
+          child.stdout.on('data', log)
+          child.stderr.on('data', log)
+
+    setCommitStatus = (sha, state) ->
+      GitHub = require('github')
+      github = new GitHub
+        version: '3.0.0'
+        protocol: 'https',
+        timeout: 5000,
+        headers:
+          'user-agent': 'github-status-reporter'
+
+      github.authenticate
+        type: 'oauth'
+        token: process.env.GITHUB_TOKEN
+
+      repo = (process.env.TRAVIS_REPO_SLUG || 'baconjs/bacon.js').split('/', 2)
+
+      q =
+        user: repo[0],
+        repo: repo[1],
+        sha: sha,
+        state: state
+        context: 'continous-integration/browserstack'
+
+      new Promise (resolve, reject) ->
+        github.statuses.create q, (error) ->
+          if error
+            reject(error)
+          else
+            resolve()
+
+    commit = (callback) ->
+      if process.env.TRAVIS_COMMIT
+        callback(process.env.TRAVIS_COMMIT)
+      else
+        require('git-rev').long(callback)
+
     done = @async()
-    git = require('git-rev')
-    git.long (sha) ->
+    commit (sha) ->
       grunt.log.write('SHA: ' + sha)
       setCommitStatus(sha, 'pending')
         .then(browserstack(grunt))


Remove the heroku task.

Remove the heroku task.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -105,5 +105,4 @@
   grunt.loadNpmTasks 'grunt-coffeelint'

   grunt.registerTask 'default', ['rubyHaml', 'less', 'coffeelint', 'coffee','copy']
-  grunt.registerTask 'server', ['default', 'connect', 'notify:server', 'open:dev', 'watch']
-  grunt.registerTask 'heroku', ['default']+  grunt.registerTask 'server', ['default', 'connect', 'notify:server', 'open:dev', 'watch']


Updated Gruntfile on the new structure

Updated Gruntfile on the new structure

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -33,7 +33,7 @@
         tasks: ["coffeelint:gruntfile"]

       src:
-        files: "src/*.coffee"
+        files: "src/**/*.coffee"
         tasks: ["coffeelint:src", "coffee:src", "nodeunit", "chalkboard"]

       test:
@@ -44,8 +44,16 @@
       options:
         join: true
       src:
-        files:
-          "chalkboard.js": ["src/*.coffee"]
+        files: [
+          {
+            expand: true
+            flatten: false
+            ext: ".js"
+            cwd: "src/"
+            src: ["**/*.coffee"]
+            dest: ""
+          }
+        ]

     chalkboard:
       src:


added tut-ascii-art

added tut-ascii-art

--- config.cson
+++ config.cson
@@ -19,5 +19,8 @@
   linter:
     showErrorPanel: false
   react: {}
-  "tool-bar":
-    visible: false
+  "tool-bar": {}
+  "tut-ascii-art":
+    font: "Ghoulish"
+    config:
+      font: {}


Added find competition by membership

Added find competition by membership

--- resources/competitions.coffee
+++ resources/competitions.coffee
@@ -1,3 +1,4 @@
+Q = require 'q'
 {models, Resource, filters} = require '../resource_helper'

 class CompetitionResource extends Resource
@@ -7,6 +8,9 @@
   getEndpoints: -> [
     getByOwner
     index
+    getByMembership
+    end
+    restart
     'show'
     'update'
     'destroy'
@@ -21,7 +25,6 @@
   route: '/by-owner/:ownerId'
   method: 'GET'
   filters: [filters.FromUrlParams, filters.FromQueryParams]
-  emptyResult: []
   handler: ->
     ownerId = @params.ownerId
     showClosed = @params.showClosed
@@ -30,19 +33,68 @@
     unless showClosed == 'true' or showClosed == true
       query.open = true
     console.log "[HTTP] request to list competitions by owner (ownerId=#{ownerId}, showClosed=#{showClosed})"
-    @api.find(query)
+    @api.list(query)

 index =
   route: '/'
   method: 'GET'
   filters: [filters.FromUrlParams, filters.FromQueryParams]
-  emptyResult: []
   handler: ->
     showClosed = @params.showClosed
     query = {}
     unless showClosed == 'true' or showClosed == true
       query.open = true
     console.log "[HTTP] request to list competitions (showClosed=#{showClosed})"
-    @api.find(query)
+    @api.list(query)
+
+getByMembership =
+  route: '/by-membership/:userId'
+  method: 'GET'
+  filters: [filters.FromUrlParams, filters.FromQueryParams]
+  handler: ->
+    userId = @params.userId
+    showClosed = @params.showClosed == 'true' or @params.showClosed == true
+    competitionsQuery = {}
+    membershipApi = @createApi(@adapter, @models.competition_membership)
+    console.log "[HTTP] request to list competitions by membership (userId=#{userId}, showClosed=#{showClosed})"
+    membershipApi.list(user_id: userId)
+    .then((memberships) =>
+      if memberships?.length == 0
+        console.log "[HTTP] found no competition memberships (userId=#{userId})"
+        return []
+
+      membershipIds = memberships.map((membership) ->
+        _id: membership.competition_id
+      )
+      anyCompetition = { $or: membershipIds }
+      dbQuery = @model.find(anyCompetition)
+      unless showClosed
+        dbQuery.where('open').equals(true)
+      Q(dbQuery.exec())
+    )
+
+end =
+  route: '/:id/end'
+  filters: [filters.FromUrlParams]
+  method: 'POST'
+  handler: ->
+    competitionId = @params.id
+    console.log "[HTTP] end competition (competitionId=#{competitionId})"
+    @api.update(
+      { _id: competitionId },
+      { open: false }
+    )
+
+restart =
+  route: '/:id/restart'
+  filters: [filters.FromUrlParams]
+  method: 'POST'
+  handler: ->
+    competitionId = @params.id
+    console.log "[HTTP] start competition (competitionId=#{competitionId})"
+    @api.update(
+      { _id: competitionId },
+      { open: true }
+    )

 module.exports = new CompetitionResource


send proper eslResponse

send proper eslResponse

--- lib/esl.coffee
+++ lib/esl.coffee
@@ -221,7 +221,7 @@
   register_callback: (event,cb) ->
     @socket.on event, (res) =>
       @socket.removeAllListeners event
-      cb.apply res
+      cb res

   # A generic way of sending commands to FreeSwitch.
   #
@@ -360,8 +360,7 @@
   # The callback is only called when the command has completed.
   command_uuid: (uuid,app_name,app_arg,cb) ->
     if cb?
-      @socket.on "CHANNEL_EXECUTE_COMPLETE #{app_name} #{app_arg}", (res) ->
-        cb.apply res
+      @socket.on "CHANNEL_EXECUTE_COMPLETE #{app_name} #{app_arg}", cb
     @execute_uuid uuid,app_name,app_arg

   # Hangup a call
@@ -411,10 +410,10 @@
   socket.on 'end',  ()     ->  parser.on_end()

   # Make the command responses somewhat unique.
-  socket.on 'CHANNEL_EXECUTE_COMPLETE', ->
+  socket.on 'CHANNEL_EXECUTE_COMPLETE', (res) ->
     application = @body['Application']
     application_data = @body['Application-Data']
-    socket.emit "#{event_name} #{application} #{application_data}", @
+    socket.emit "#{event_name} #{application} #{application_data}", res

   parser.process = (headers,body) ->
     if exports.debug


Code style: Fix indent.

Code style: Fix indent.

--- lib/ipc.coffee
+++ lib/ipc.coffee
@@ -18,7 +18,7 @@
 #   })
 #
 #   nameIPC = ipc.access("plugin-name")
-#	  if nameIPC?
+#   if nameIPC?
 #     nameIPC.api_name(args)
 #


Fix naming of variables in handleImage

Fix naming of variables in handleImage

Collided with reserved words

--- Learn-server/handleImage.coffee
+++ Learn-server/handleImage.coffee
@@ -10,10 +10,10 @@
   return callback("No `image` file. Make sure the `image` field is being set.") unless file

   _path = path.normalize("#{global.image_path}#{createHashID()}_#{file.name}")
-  is = fs.createReadStream file.path
-  os = fs.createWriteStream _path
+  input = fs.createReadStream file.path
+  output = fs.createWriteStream _path

-  is.pipe os
-  is.on 'end', ->
+  input.pipe output
+  input.on 'end', ->
     fs.unlinkSync file.path
     callback null, _path


Update vansteki.giphy.coffee

Update vansteki.giphy.coffee

--- scripts/vansteki.giphy.coffee
+++ scripts/vansteki.giphy.coffee
@@ -16,5 +16,3 @@
     getRandomGiphyGif(msg, tags)
   robot.hear /bitch/ig, (msg) ->
     getRandomGiphyGif(msg, 'bitch')
-  robot.hear /婊子/ig, (msg) ->
-    msg.send "在哪? 我褲子都脫了!"


Fix callback in renameRoutes

Fix callback in renameRoutes

--- server/models/template.coffee
+++ server/models/template.coffee
@@ -35,8 +35,5 @@
   makePrimary(@primary, next)

 Template.statics.renameRoutes = (oldName, newName, callback) ->
-  Route.update({ template: oldName }, { template: newName }, (err, n) ->
-    callback(err)
-  )
-
+  Route.update({ template: oldName }, { template: newName }, callback)
 module.exports = db.model('Template', Template)


Team: Forms: Minor changes

Team: Forms: Minor changes

--- client/landing/site.landing/coffee/team/forms/teamjoinwithinvitedaccountform.coffee
+++ client/landing/site.landing/coffee/team/forms/teamjoinwithinvitedaccountform.coffee
@@ -1,6 +1,6 @@
 JView           = require './../../core/jview'
+LoginInputView  = require './../../login/logininputview'
 TeamJoinTabForm = require './../forms/teamjointabform'
-LoginInputView  = require './../../login/logininputview'

 module.exports = class TeamJoinWithInvitedAccountForm extends TeamJoinTabForm
@@ -9,7 +9,7 @@

     super

-    teamData     = KD.utils.getTeamData()
+    teamData = KD.utils.getTeamData()

     @username = new LoginInputView
       cssClass        : 'hidden'


remove specs on function returns

remove specs on function returns

--- spec/index.coffee
+++ spec/index.coffee
@@ -34,9 +34,6 @@
       @maypop.after.bind(undefined).should.throw()
       @maypop.before.bind(undefined, ->).should.not.throw()
       @maypop.after.bind(undefined, ->).should.not.throw()
-    it 'should return the function created by maypop when executing "before" or "after"', ->
-      @maypop.before(->).should.equal @maypop
-      @maypop.after(->).should.equal @maypop
     it 'should run the function passed to "before" before the original function', ->
       spy = sinon.spy()
       @maypop.before spy


Easier to read match method. Extraxt initialisation of matcher maps into own method.

--- src/coffee/stockxmlimport.coffee
+++ src/coffee/stockxmlimport.coffee
@@ -10,6 +10,8 @@
   @_options = options
   @sync = new InventorySync Config
   @rest = @sync._rest
+  @existingStocks = {}
+  @sku2index = {}
   @

 exports.StockXmlImport.prototype.elasticio = (msg, cfg, cb, snapshot) ->
@@ -21,7 +23,8 @@
       xmlString = new Buffer(content, 'base64').toString()
       @run xmlString, cb
   else if msg.body
-    @createOrUpdate([@createEntry(msg.body.SKU, msg.body.QUANTITY)], cb)
+    @initMatcher().then () =>
+      @createOrUpdate([@createEntry(msg.body.SKU, msg.body.QUANTITY)], cb)
   else
     @returnResult false, 'No data found in elastic.io msg.', cb

@@ -29,12 +32,13 @@
   throw new Error 'String required' unless _.isString xmlString
   throw new Error 'Callback must be a function' unless _.isFunction callback

-  xmlHelpers.xmlTransform xmlHelpers.xmlFix(xmlString), (err, result) =>
-    if err
-      @returnResult false, "Error on parsing XML: " + err, callback
-    else
-      stocks = @mapStock result.root, "TODO"
-      @createOrUpdate stocks, callback
+  @initMatcher().then () =>
+    xmlHelpers.xmlTransform xmlHelpers.xmlFix(xmlString), (err, result) =>
+      if err
+        @returnResult false, "Error on parsing XML: " + err, callback
+      else
+        stocks = @mapStock result.root, "TODO"
+        @createOrUpdate stocks, callback

 exports.StockXmlImport.prototype.returnResult = (positiveFeedback, msg, callback) ->
   d =
@@ -45,36 +49,42 @@
   callback d

 exports.StockXmlImport.prototype.createOrUpdate = (stocks, callback) ->
+  posts = []
+  bar = new ProgressBar 'Updating stock [:bar] :percent done', { width: 50, total: stocks.length }
+  for s in stocks
+    es = @match(s)
+    if es
+      posts.push @update(s, es, bar)
+    else
+      posts.push @create(s, bar)
+
+  Q.all(posts).then (v) =>
+    if v.length is 1
+      v = v[0]
+    else
+      v = "#{v.length} Done"
+    @returnResult true, v, callback
+  .fail (v) =>
+    @returnResult false, v, callback
+
+exports.StockXmlImport.prototype.initMatcher = (rest) ->
+  deferred = Q.defer()
   @rest.GET "/inventory?limit=0", (error, response, body) =>
     if error
-      @returnResult false, 'Problem on getting existing stock information.', callback
+      deferred.reject 'Problem on getting existing stock information.'
       return
     if response.statusCode != 200
-      @returnResult false, 'Can not fetch stock information.', callback
+      deferred.reject 'Can not fetch stock information.'
       return
-    existingStocks = JSON.parse(body).results
-    sku2index = @match(existingStocks)
-    posts = []
-    bar = new ProgressBar 'Updating stock [:bar] :percent done', { width: 50, total: stocks.length }
-    for s in stocks
-      if sku2index[s.sku] >= 0
-        posts.push @update(s, existingStocks[sku2index[s.sku]], bar)
-      else
-        posts.push @create(s, bar)
-    Q.all(posts).then (v) =>
-      if v.length is 1
-        v = v[0]
-      else
-        v = "#{v.length} Done"
-      @returnResult true, v, callback
-    .fail (v) =>
-      @returnResult false, v, callback
+    @existingStocks = JSON.parse(body).results
+    for es, i in @existingStocks
+      @sku2index[es.sku] = i
+    deferred.resolve @existingStocks
+  deferred.promise

-exports.StockXmlImport.prototype.match = (existingStocks) ->
-  sku2index = {}
-  for es, i in existingStocks
-    sku2index[es.sku] = i
-  sku2index
+exports.StockXmlImport.prototype.match = (s) ->
+  if @sku2index[s.sku] >= 0
+    @existingStocks[@sku2index[s.sku]]

 exports.StockXmlImport.prototype.update = (s, es, bar) ->
   deferred = Q.defer()


EventRouter fix

EventRouter fix

--- EventRouter.coffee
+++ EventRouter.coffee
@@ -16,11 +16,11 @@
 				iMachine = new StateMachine i.identifier, this
 				iMachine.apply "touchstart", i
 				@machines[i.identifier] = iMachine
-				@fingerCount = event.touches.length

 	touchend: (event) ->
 		event.preventDefault()
+		@fingerCount = event.touches.length
 		for iMKey in @machines.keys()
 			iMKey = parseInt(iMKey)
 			exists = false
@@ -31,7 +31,8 @@
 			if !exists
 				@machines[iMKey].apply("touchend", {})
 				delete @machines[iMKey]
-
+
+		@grouper.refreshFIngerCount @fingerCount

 	touchmove: (event) ->
@@ -51,13 +52,16 @@
 class EventGrouper
 	constructor: ->
 		@savedTap = {}
+		@fingerCount = 0

+	refreshFingerCount: (newCount) ->
+		if @fingerCount != newCount
+			@fingerCount = newCount
+			@analyser = new Analyser @fingerCount, element
+
 	receive: (name, eventObj, fingerCount, element) ->
+		@refreshFingerCount fingerCount

-		if @fingerCount != fingerCount
-			@fingerCount = fingerCount
-			@analyser = new Analyser @fingerCount, element
-
 		##
 		if name == "tap"
 			if @savedTap[eventObj.identifier]? && ((new Date().getTime()) - @savedTap[eventObj.identifier].time) < 400


added Google Analytics

added Google Analytics

--- src/components/head/index.coffee
+++ src/components/head/index.coffee
@@ -114,6 +114,19 @@
         type: 'text/css'
         href: 'https://fonts.googleapis.com/icon?family=Material+Icons'

+      # Analytics
+      z 'script',
+        innerHTML: '''
+          (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||
+          function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();
+          a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;
+          m.parentNode.insertBefore(a,m)})(window,document,'script',
+          'https://www.google-analytics.com/analytics.js','ga');
+
+          ga('create', 'UA-30570115-19', 'auto');
+          ga('send', 'pageview');
+        '''
+
       # styles
       z 'style.styles',
         innerHTML: if isInline then serverData?.styles else null


pop image url

pop image url

--- lib/notifier.coffee
+++ lib/notifier.coffee
@@ -5,6 +5,8 @@
 atomHelper = require './atom-helper'
 path = require 'path'
 io = require 'socket.io-client'
+remote = require 'remote'
+BrowserWindow = remote.require('browser-window')

 module.exports =
 class Notifier extends EventEmitter


rethinking direct action

rethinking direct action

--- src/layout/mixins/actions.coffee
+++ src/layout/mixins/actions.coffee
@@ -23,6 +23,17 @@
     console.log 'send_message', message, sprite
     sprite.send 'apply', message

+  # TODO: everyone should step on direct action
+  # Implies button should:
+  # * move current buffer to the end
+  # * append action
+  # * step everyone
+  #
+  # which implies the programs buffer should
+  # * not wrap next_pointer until beforehand
+  # * have explicit repeats after first and repeat
+  # * move up to just before calling repeat (?)
+
   buttons = make.buttons(
     'command',
     actions,


nullify the prefix and suffix when changing the language

--- src/aether.coffee
+++ src/aether.coffee
@@ -369,6 +369,8 @@
     optionsValidation = optionsValidator (language: lang)
     throw new Error("Options array is not valid: " + JSON.stringify(optionsValidation.errors, null, 4)) if not optionsValidation.valid
     @options.language = lang
+    @wrappedCodePrefix = null
+    @wrappedCodeSuffix = null
     @reset()
     return lang


Missed something

Missed something

--- src/badges.coffee
+++ src/badges.coffee
@@ -73,6 +73,7 @@
   'interitus': 1,
   'liberator': 5,
   'mindcontroller': 5,
+  'missionday': 1,
   'nl1331' : 1,
   'oliver-lynton-wolfe': 1,
   'perseopolis': 1,


refactor Hash::meta

refactor Hash::meta

--- src/batman.coffee
+++ src/batman.coffee
@@ -960,17 +960,17 @@
   toJSON: @::toObject

 class Batman.Hash extends Batman.Object
+  class @Metadata extends Batman.Object
+    constructor: (@hash) ->
+    @accessor 'length', ->
+      @hash.registerAsMutableSource()
+      @hash.length
+    @accessor 'isEmpty', -> @hash.isEmpty()
+    @accessor 'keys', -> @hash.keys()
+
   constructor: ->
+    @meta = new @constructor.Metadata(this)
     Batman.SimpleHash.apply(@, arguments)
-    # Add a meta object to all hashes which we can then use in the `meta` filter to allow binding
-    # to hash meta-properties without reserving keys.
-    @meta = new Batman.Object
-    self = this
-    @meta.accessor 'length', ->
-      self.registerAsMutableSource()
-      self.length
-    @meta.accessor 'isEmpty', -> self.isEmpty()
-    @meta.accessor 'keys', -> self.keys()
     super

   $extendsEnumerable(@::)
@@ -1760,9 +1760,24 @@
     else
       location.replace(@normalizePath("#{Batman.config.pathPrefix}#{@linkTo(realPath)}"))

-
 Batman.redirect = $redirect = (url) ->
   Batman.navigator?.redirect url
+
+class Batman.ParamsManager extends Batman.Hash
+  @accessor
+    get: (k) -> @params.get(k)
+    set: @mutation (k,v) -> @params.set(k,v)
+    unset: @mutation (k) -> @params.unset(k)
+    cachable: false
+  constructor: (@navigator, @params) ->
+
+class Batman.ParamsReplacer extends Batman.ParamsManager
+  redirect: -> @navigator.replace(@toObject())
+
+class Batman.ParamsPusher extends Batman.ParamsManager
+  redirect: -> @navigator.push(@toObject())
+
+

 # Route Declarators
 # -----------------


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera.coffee
+++ src/camera.coffee
@@ -1,4 +1,3 @@
-oldCamera = Camera
 Camera = (I={}) ->
   Object.reverseMerge I,
     cameraBounds: Rectangle # World Coordinates


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera.coffee
+++ src/camera.coffee
@@ -40,7 +40,7 @@
     else if objectCenter.x < centerRect.left
       I.scroll.x = I.scroll.x.approach(centerRect.left, I.maxVelocity.x)

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


Add 'hello' message handling

Add 'hello' message handling

--- src/client.coffee
+++ src/client.coffee
@@ -208,7 +208,7 @@
                 @emit 'ping'
             when 'posted'
                 @emit 'message', m
-            when 'typing', 'post_edit', 'post_deleted', 'user_added', 'user_removed', 'status_change'
+            when 'hello', 'typing', 'post_edit', 'post_deleted', 'user_added', 'user_removed', 'status_change'
                 # Generic handler
                 @emit message.event, message
             when 'channel_viewed', 'preference_changed', 'ephemeral_message'
@@ -224,7 +224,7 @@
                     @_lastPong = Date.now()
                     @emit 'ping', message
                 else
-                    @logger.debug 'Received unhandled message type: '+message.event
+                    @logger.debug 'Received unhandled message:'
                     @logger.debug message

     getUserByID: (id) ->


Break and continue are yellow.

Break and continue are yellow.

--- src/coffee.coffee
+++ src/coffee.coffee
@@ -112,6 +112,11 @@
     'loadscript'
   ]

+  STATEMENT_KEYWORDS = [
+    'break'
+    'continue'
+  ]
+
   OPERATOR_PRECEDENCES =
     '||': 1
     '&&': 2
@@ -361,6 +366,15 @@

           else
             @mark node.base, depth + 1, precedence, wrappingParen, indentDepth
+
+        # ### Keywords ###
+        when 'Literal'
+          if node.value in STATEMENT_KEYWORDS
+            # handle break and continue
+            @addBlock node, depth, 0, 'return', wrappingParen, BLOCK_ONLY
+          else
+            # otherwise, leave it as a white block
+            0

         # ### Literal ###
         # No-op. Translate directly to text
@@ -790,7 +804,7 @@
           throw firstError

   fixCoffeeScriptError = (lines, e) ->
-    console.log 'encountered error', e.message, 'line',  e.location.first_line
+    console.log 'encountered error', e.message, 'line',  e.location?.first_line
     if /unexpected\s*(?:newline|if|for|while|switch|unless|end of input)/.test(
         e.message) and /^\s*(?:if|for|while|unless)\s+\S+/.test(
         lines[e.location.first_line])


Add support for 'in' operator

Add support for 'in' operator

--- src/coffee.coffee
+++ src/coffee.coffee
@@ -173,6 +173,15 @@
         when 'Existence'
           addBlock node, 7, colors.VALUE, wrappingParen
           mark node.expression, 7
+
+        # ### In ###
+        # The In operator has precedence
+        # equivalent to a function call.
+        when 'In'
+          addBlock node, 0, colors.VALUE, wrappingParen
+
+          mark node.object, 0
+          mark node.array, 0

         # ### Value ####
         # A Value is not of much use to the ICE


don't dump the entire cache in the log

don't dump the entire cache in the log

--- chrome/content/zotero-better-bibtex/keymanager.coffee
+++ chrome/content/zotero-better-bibtex/keymanager.coffee
@@ -159,7 +159,7 @@
     libraryID = @integer(if item.libraryID == undefined then Zotero.DB.valueQuery('select libraryID from items where itemID = ?', [item.itemID]) else item.libraryID)
     itemID = @integer(item.itemID)
     in_use = (key.citekey for key in @keys.where((o) -> o.libraryID == libraryID && o.itemID != itemID && o.citekey.indexOf(citekey) == 0 && (o.citekey.length - citekey.length) in [0, 1]))
-    Zotero.BetterBibTeX.debug("keymanager.free: find free key default=#{citekey}, taken:", in_use, 'cache:', @keys.where((o) -> true))
+    Zotero.BetterBibTeX.debug("keymanager.free: find free key default=#{citekey}, taken:", in_use)
     postfix = { n: -1, c: '' }
     while (citekey + postfix.c) in in_use
       postfix.n++


Sidebar: to not to show vm settings modal before making the machine run

--- client/Social/Activity/sidebar/activitysidebar.coffee
+++ client/Social/Activity/sidebar/activitysidebar.coffee
@@ -402,9 +402,6 @@

       machineItem.on 'click', @lazyBound 'handleMachineItemClick', machineItem

-
-
-
     if KD.userMachines.length
     then @listMachines KD.userMachines
     else @fetchMachines @bound 'listMachines'
@@ -412,12 +409,18 @@

   handleMachineItemClick: (machineItem, event) ->

-    machine = machineItem.getData()
+    machine  = machineItem.getData()
+    {status} = machine
+    {Building, Running} = Machine.State

     if event.target.nodeName is 'SPAN'

-      KD.utils.stopDOMEvent event
-      KD.singletons.mainView.openMachineModal machine, machineItem
+      if status.state is Running
+        KD.utils.stopDOMEvent event
+        KD.singletons.mainView.openMachineModal machine, machineItem
+      else
+        return
+

     else if event.target.nodeName is 'CITE' and machineItem.type is 'machine'


Include `cached_from` info in cached library tags.

--- src/renderer.coffee
+++ src/renderer.coffee
@@ -34,9 +34,11 @@
         key = getCacheKey(lib)
         if key
             url = key
+            cached_from = "data-cached_from='#{ lib }'"
         else
             url = lib
-        script_libs += "<script src='#{ url }'></script>"
+            cached_from = ''
+        script_libs += "<script src='#{ url }' #{ cached_from }></script>"
     return script_libs

 compileStyleLibraries = (style_libraries) ->


latency compensation: fake data now has group data too.

--- client/app/Applications/Activity.kdapplication/widgets/widgetcontroller.coffee
+++ client/app/Applications/Activity.kdapplication/widgets/widgetcontroller.coffee
@@ -149,6 +149,7 @@
       fakeTag       = $.extend {},fakeTag,
         title       : tag.title or tag.$suggest
         body        : tag.title or tag.$suggest
+        group       : tag.group or 'koding'
         counts      :
           followers : 0
           following : 0
@@ -158,7 +159,6 @@
     tags

   createFakeDataStructureForOwner = (activity)->
-
     oldActivity = activity
     constructorName = activity.fakeType
     # prepare fake post
@@ -168,6 +168,7 @@
       slug        : 'fakeActivity'
       title       : activity.title or activity.body
       body        : activity.body
+      group       : activity.group or 'koding'
       html        : KD.utils.applyMarkdown activity.body
       counts      :
         followers : 0


payment: remove a console.log

payment: remove a console.log

--- client/app/Applications/Dashboard.kdapplication/views/planadmincontrols.coffee
+++ client/app/Applications/Dashboard.kdapplication/views/planadmincontrols.coffee
@@ -2,12 +2,10 @@

   viewAppended: ->
     plan = @getData()
-
+
     @addProductsButton = new KDButtonView
       title    : "Add products"
       callback : => @emit 'AddProductsRequested', plan
-
-    console.log @constructor

     super()


Replace RootView.getActiveEditor() with getActiveView()

--- spec/autocomplete-spec.coffee
+++ spec/autocomplete-spec.coffee
@@ -17,8 +17,8 @@
       autocompletePackage = window.loadPackage("autocomplete")
       expect(AutocompleteView.prototype.initialize).not.toHaveBeenCalled()

-      leftEditor = rootView.getActiveEditor()
-      rightEditor = rootView.getActiveEditor().splitRight()
+      leftEditor = rootView.getActiveView()
+      rightEditor = leftEditor.splitRight()

       leftEditor.trigger 'autocomplete:attach'
       expect(leftEditor.find('.autocomplete')).toExist()


Adding the ability for javascript from problems to execute more intelligently; for example, errors from javascript included in the returned html will show up in the console when using this mechanism.

--- common/lib/xmodule/xmodule/js/src/capa/display.coffee
+++ common/lib/xmodule/xmodule/js/src/capa/display.coffee
@@ -31,7 +31,19 @@
     else
       $.postWithPrefix "#{@url}/problem_get", (response) =>
         @el.html(response.html)
+        @executeProblemScripts()
         @bind()
+
+  executeProblemScripts: ->
+    @el.find(".script_placeholder").each (index, placeholder) ->
+      s = $("<script>")
+      s.attr("type", "text/javascript")
+      s.attr("src", $(placeholder).attr("data-src"))
+
+      # Need to use the DOM elements directly or the scripts won't execute
+      # properly.
+      $('head')[0].appendChild(s[0])
+      $(placeholder).remove()

   check: =>
     Logger.log 'problem_check', @answers


Testes iniciais para SkuSelector

Testes iniciais para SkuSelector

--- spec/SkuSelectorSpec.coffee
+++ spec/SkuSelectorSpec.coffee
@@ -1,27 +1,61 @@
 jasmine.getFixtures().fixturesPath = "base/build/spec/fixtures"
 jasmine.getJSONFixtures().fixturesPath = "base/build/spec/mocks"

-describe 'SkuSelector', ->
-	it 'should exist', ->
-		expect(vtex.portalPlugins.SkuSelector).toBeDefined()

-describe '$.skuSelector', ->
-
+describe 'SkuSelector Plugin', ->
 	beforeEach ->
-		loadFixtures 'sku-selector.html'
-		loadJSONFixtures('oneDimensionAvailable.json', 'threeDimensionsAvailable.json', 'threeDimensionsSomeUnavailable.json')
-
-	it 'should have jQuery', ->
-		expect($).toBeDefined()
-	it 'should exist', ->
-		expect($.fn.skuSelector).toBeDefined()
-		expect($.skuSelector).toBeDefined()
-
-	it 'should have loaded HTML fixtures correctly', ->
-		expect($('.sku-selector-container')).toExist()
+		mockNames = ['oneDimensionAvailable.json', 'threeDimensionsAvailable.json', 'threeDimensionsSomeUnavailable.json']
+		loadJSONFixtures(mockNames...)
+		@mocks = (getJSONFixture(name) for name in mockNames)

 	it 'should have loaded JSON fixtures correctly', ->
-		mock1 = getJSONFixture('oneDimensionAvailable.json')
-		expect(mock1).toBeDefined()
-		expect(typeof mock1).toBe(typeof {})
+		expect(@mocks[0]).toBeDefined()
+		expect(typeof @mocks[0]).toBe(typeof {})

+	describe 'SkuSelector Class', ->
+		beforeEach ->
+			@mock = @mocks[0]
+			@ss = new vtex.portalPlugins.SkuSelector(@mock)
+
+		it 'should exist', ->
+			expect(vtex.portalPlugins.SkuSelector).toBeDefined()
+
+		it 'should be instantiated', ->
+			expect(@ss instanceof vtex.portalPlugins.SkuSelector).toBe(true)
+
+		it 'should import productId', ->
+			expect(@ss.productId).toEqual(@mock.productId)
+
+		it 'should import name', ->
+			expect(@ss.name).toEqual(@mock.name)
+
+		it 'should import dimensions', ->
+			expect(@ss.dimensions).toEqual(@mock.dimensions)
+
+		it 'should import skus', ->
+			expect(@ss.skus).toEqual(@mock.skus)
+
+		describe 'selectedDimensionsMap', ->
+			it 'should initially be filled with undefined', ->
+				for selectedDimension in @ss.selectedDimensionsMap
+					expect(selectedDimension).toBeUndefined()
+
+			it 'should have the same length as dimensions', ->
+				expect(Object.keys(@ss.selectedDimensionsMap).length).toEqual(@ss.dimensions.length)
+
+
+	describe '$.skuSelector', ->
+		beforeEach ->
+			loadFixtures 'sku-selector.html'
+
+		it 'should have jQuery', ->
+			expect($).toBeDefined()
+		it 'should exist', ->
+			expect($.fn.skuSelector).toBeDefined()
+			expect($.skuSelector).toBeDefined()
+
+		it 'should have loaded HTML fixtures correctly', ->
+			expect($('.sku-selector-container')).toExist()
+
+
+


Added return_to_url and return_to_text to regular extended_fact_page

--- core/app/backbone/controllers/facts_controller.coffee
+++ core/app/backbone/controllers/facts_controller.coffee
@@ -15,8 +15,14 @@
     window.efv = new ExtendedFactView(model: fact)
     @main.contentRegion.show(efv)

-    window.extended_fact_title_view = new ExtendedFactTitleView( model: fact )
-    @main.titleRegion.show( extended_fact_title_view )
+    username = fact.get('created_by').username
+
+    title_view = new ExtendedFactTitleView(
+                        model: fact,
+                        return_to_url: username,
+                        return_to_text: username.capitalize() )
+
+    @main.titleRegion.show( title_view )

     @showChannelListing(fact.get('created_by').username)


Removed never used transition code

Removed never used transition code

--- core/app/backbone/factlink/detached_view_cache.coffee
+++ core/app/backbone/factlink/detached_view_cache.coffee
@@ -2,7 +2,6 @@
   constructor: (options) ->
     super(options)
     @_view = null
-    @_transition = options?.transition

   currentView: -> @_view
   cleanup: -> @remove()
@@ -13,22 +12,13 @@

     @$el.append(new_view.$el)

-    if @_view?
-      @transitionView(new_view, @_view)
+    @_view.$el.detach()

     @_view = new_view
-
-  transitionView: (new_view, old_view) ->
-    if @_transition?
-      @_transition.run old_view.$el, new_view.$el, ->
-        old_view.$el.detach()
-    else
-      old_view.$el.detach()

   removeView: ->
       @_view.$el.detach()
       @_view = null
-

 class Backbone.Factlink.DetachedViewCache extends Backbone.Factlink.DetachedViewPort


Removed useless code onClose

Removed useless code onClose

--- core/app/backbone/views/facts/fact_bottom_view.coffee
+++ core/app/backbone/views/facts/fact_bottom_view.coffee
@@ -12,8 +12,6 @@
         url.href = @fact_url

         url.host
-
-  onClose: -> @addToChannelView?.close()

   showAddToChannel: (e) ->
     e.preventDefault()


Move opinion area underneath top annotation area

--- core/app/backbone/views/facts/react_discussion.coffee
+++ core/app/backbone/views/facts/react_discussion.coffee
@@ -68,9 +68,10 @@
           else
             _div ["loading-indicator-centered"],
               ReactLoadingIndicator()
-        if @canHaz('opinions_of_users_and_comments')
-          ReactOpinionateArea
-            model: @model().getOpinionators()
+
+      if @canHaz('opinions_of_users_and_comments')
+        ReactOpinionateArea
+          model: @model().getOpinionators()
       ReactAddComment
         model: @model().comments()
         initiallyFocus: @props.initiallyFocusAddComment


OHM-106 #428: ensureIndex rather than createIndex

--- src/middleware/visualizer.coffee
+++ src/middleware/visualizer.coffee
@@ -138,7 +138,7 @@

   now = new Date
   event.created = now for event in trxEvents
-  events.VisualizerEvent.collection.createIndex { created: 1 }, { expireAfterSeconds: 600 }, ->
+  events.VisualizerEvent.collection.ensureIndex { created: 1 }, { expireAfterSeconds: 600 }, ->
     events.VisualizerEvent.collection.insert trxEvents, (err) -> return if err then done err else done()


install handler for receiving snapshots of container ids

--- imports/server/mqtt/mqtt.coffee
+++ imports/server/mqtt/mqtt.coffee
@@ -1,19 +1,21 @@
 mqtt    = require 'mqtt'
 dot     = require 'mongo-dot-notation'

-storage = require './agent/storage.coffee'
-logs    = require './agent/logs.coffee'
+storage  = require './agent/storage.coffee'
+logs     = require './agent/logs.coffee'
+snapshot = require './docker/snapshot.coffee'

 mbe = Meteor.bindEnvironment

 mqttTopicHandlerMap =
-  '/docker/events':             mbe require './docker/events.coffee'
-  '/docker/container/inspect':  mbe require './docker/inspect.coffee'
-  '/agent/docker/pulling':      mbe require './agent/pulling.coffee'
-  '/agent/docker/log/startup':  mbe logs.startup
-  '/agent/docker/log/teardown': mbe logs.teardown
-  '/agent/storage/buckets':     mbe storage.buckets
-  '/agent/storage/size':        mbe storage.size
+  '/docker/events':                 mbe require './docker/events.coffee'
+  '/docker/container/inspect':      mbe require './docker/inspect.coffee'
+  '/agent/docker/pulling':          mbe require './agent/pulling.coffee'
+  '/agent/docker/log/startup':      mbe logs.startup
+  '/agent/docker/log/teardown':     mbe logs.teardown
+  '/agent/storage/buckets':         mbe storage.buckets
+  '/agent/storage/size':            mbe storage.size
+  '/docker/snapshot/containerIds':  mbe snapshot.containerIds

 mqst = Meteor.settings.mqtt
 client = mqtt.connect mqst.url,


Refactor `make_tree_node` and fix line numbers.

* Use new `TreeNode` class instead of building "magic" objects.
* Observe line offset when building nodes.

--- src/CoffeeScriptParser.coffee
+++ src/CoffeeScriptParser.coffee
@@ -242,44 +242,43 @@
         else
             print_error message...

+    # Build a node of the structure tree.
+    # It must have a constructor with the same signature as this function and
+    # a method `add` for adding child nodes.
     make_tree_node: (name, type, qualifier, first_line, last_line) ->
+        first_line += @config.line if first_line?
+        last_line += @config.line if last_line?
         if @config.makeTreeNode?
-            @config.makeTreeNode.apply(@config, arguments)
+            @config.makeTreeNode(name, type, qualifier, first_line, last_line)
         else
-            child_nodes = []
-            add: (node) -> child_nodes.push node
-            pprint: -> @_pprint().join('')
-            _pprint: (parts=[], prefix, last) ->
-                if prefix?
-                    parts.push prefix
-                    if last
-                        parts.push ' └─ '
-                        prefix = prefix+'    '
-                    else
-                        parts.push ' ├─ '
-                        prefix = prefix+' │  '
+            new TreeNode(name, type, qualifier, first_line, last_line)
+
+    # A simple structure tree / node for console output and testing.
+    class TreeNode
+        PREFIX = static: '@', hidden: '-', property: ' ', constructor: ' '
+        POSTFIX = class: ' class', task: ' task'
+        constructor: (name, type, qualifier, first_line, last_line) ->
+            @children = []
+            @name_display = [ PREFIX[qualifier], name, POSTFIX[type] ]
+            if first_line?
+                @name_display.push ' [', first_line, '..', last_line, ']'
+        add: (node) ->
+            @children.push node
+        pprint: (parts=[], prefix='', is_last) ->
+            if is_not_root = parts.length
+                parts.push prefix
+                if is_last
+                    parts.push  ' └─ '
+                    prefix +=   '    '
                 else
-                    prefix = ''
-                switch qualifier
-                    when 'static'
-                        parts.push '@'
-                    when 'hidden'
-                        parts.push '-'
-                    when 'property', 'constructor'
-                        parts.push ' '
-                parts.push name
-                switch type
-                    when "class"
-                        parts.push ' class'
-                    when "task"
-                        parts.push ' task'
-                if first_line?
-                    parts.push ' [', first_line, '..', last_line, ']'
+                    parts.push  ' ├─ '
+                    prefix +=   ' │  '
+            parts.push @name_display...
+            last_index = @children.length-1
+            for child, i in @children
                 parts.push '\n'
-                last = child_nodes.length-1
-                for child, i in child_nodes
-                    child._pprint(parts, prefix, i is last)
-                return parts
+                child.pprint(parts, prefix, i is last_index)
+            return parts.join('') unless is_not_root

     #### AST walker
     walk_ast: (nodes, parent, parent_last_line, parent_class, in_prototype) ->


add monitor config

add monitor config

--- src/module/project_config.coffee
+++ src/module/project_config.coffee
@@ -5,6 +5,7 @@
 REMOTE_CONFIG = "remoteConf"
 LOCAL_CONFIG = "localConf"
 ZOOKEEPER_CONFIG = "zookeeperConf"
+MONITOR = "monitor"

 class ProjectConfig
 	constructor : ->
@@ -31,12 +32,16 @@
 	getServiceDiscovery : ->
 		@_configs[SERVICE_DISCOVERY]

+	getMonitor : ->
+		@_configs[MONITOR]
+
 	_validate : ->
 		@._validateConfigSource()
 		._checkNodeIsNull(SERVICE_DISCOVERY)
 		._checkNodeIsNull(REMOTE_CONFIG)
 		._checkNodeIsNull(LOCAL_CONFIG)
 		._checkNodeIsNull(ZOOKEEPER_CONFIG)
+		._checkNodeIsNull(MONITOR)
 		return true

 	_validateConfigSource : ()->


:lipstick: Remove accidental newline

:lipstick: Remove accidental newline

--- src/cursor.coffee
+++ src/cursor.coffee
@@ -25,7 +25,6 @@
       {oldHeadScreenPosition, newHeadScreenPosition} = e
       {oldHeadBufferPosition, newHeadBufferPosition} = e
       {textChanged} = e
-
       return if oldHeadScreenPosition.isEqual(newHeadScreenPosition)

       @needsAutoscroll ?= @isLastCursor() and !textChanged


export ace, emit 'save' and 'open' events

--- src/editor.coffee
+++ src/editor.coffee
@@ -10,7 +10,7 @@

 Chrome.addPane 'main', '

'

-editor = ace.edit "editor"
+exports.ace = editor = ace.edit "editor"
 editor.setTheme require "ace/theme/twilight"
 JavaScriptMode = require("ace/mode/javascript").Mode
 CoffeeMode = require("ace/mode/coffee").Mode
@@ -32,6 +32,7 @@
   File.write filename, editor.getSession().getValue()
   setMode()
   Chrome.setDirty false
+  editor._emit 'save', { filename }
 exports.open = open = (path) ->
   filename = path

@@ -49,6 +50,7 @@
       editor.getSession().setValue File.read filename
       setMode()
       Chrome.setDirty false
+  editor._emit 'open', { filename }
 setMode = ->
   if /\.js$/.test filename
     editor.getSession().setMode new JavaScriptMode


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine.coffee
+++ src/engine.coffee
@@ -144,7 +144,7 @@

     step = ->
       if !I.paused || frameAdvance
-        elapsedTime =
+         =
         secondsPerFrame = (1 / I.FPS)
         update(secondsPerFrame)
         I.age += 1


Refactored; added docs and tests.

Refactored; added docs and tests.

--- src/scripts/github-issues.coffee
+++ src/scripts/github-issues.coffee
@@ -1,41 +1,87 @@
 # Show open issues from a Github repository.
 #
-# You need to set the following variables:
-#   HUBOT_GITHUB_TOKEN ="<oauth token>"
-#   HUBOT_GITHUB_USER ="<user name>"
+# You need to set the following variable:
+#   HUBOT_GITHUB_TOKEN = "<oauth token>"
 #
-# HUBOT_GITHUB_USER is optional, but if you set it, you can ask `show me issues
-# for hubot` instead of `show me issues for github/hubot`.
+# You may optionally set the following variables:
+#   HUBOT_GITHUB_USER = "<default user/org name>"
+#   HUBOT_GITHUB_REPO = "<default repo>"
+#   HUBOT_GITHUB_USER_(.*) = "<GitHub username for matching chat handle>"
 #
-# HUBOT_GITHUB_REPO is optional, but if you set it, you can ask `show me issues`
-# instead of `show me issues for github/hubot`.
+# If HUBOT_GITHUB_USER is set, you can ask `show me issues for hubot` instead
+# of `show me issues for github/hubot`.
 #
-# show me issues for <user/repo> -- Shows open issues for that project.
+# If HUBOT_GITHUB_REPO is set, you can ask `show me issues` instead of `show
+# me issues for github/hubot`.
+#
+# If, for example, HUBOT_GITHUB_USER_JOHN is set to GitHub user login
+# 'johndoe1', you can ask `show john's issues` instead of `show johndoe1's
+# issues`. This is useful for mapping chat handles to GitHub logins.
+#
+# show me N of ASSIGNEE's LABEL issues for REPO about QUERY -- Shows open GitHub issues for repo.

 _  = require("underscore")
 _s = require("underscore.string")

-get_username = (name) ->
-  name = name.replace("@", "").replace("'s", "")
+ASK_REGEX = ///
+  show\s            # Start's with 'show'
+  (me)?\s*          # Optional 'me'
+  (\d+|\d+\sof)?\s* # 'N of' -- 'of' is optional but ambiguous unless assignee is named
+  (\S+'s|my)?\s*    # Assignee's name or 'my'
+  (\S+)?\s*         # Optional label name
+  issues\s*         # 'issues'
+  (for\s\S+)?\s*    # Optional 'for <repo>'
+  (about\s.+)?      # Optional 'about <query>'
+///i
+
+# Given the text sent to robot.respond (e.g. 'hubot show me...'), parse the
+# criteria used for filtering issues.
+parse_criteria = (ask) ->
+  [me, limit, assignee, label, repo, query] = ask.match(ASK_REGEX)[1..]
+  me: me,
+  limit: parseInt limit.replace(" of", "") if limit?,
+  assignee: assignee.replace("'s", "") if assignee?,
+  label: label,
+  repo: repo.replace("for ", "") if repo?,
+  query: query.replace("about ", "") if query?
+
+# Filter the issue list by criteria.
+filter_issues = (issues, criteria) ->
+  { me, limit, assignee, label, repo, query } = criteria
+  if assignee?
+    issues = _.filter issues, (i) -> i.assignee? and i.assignee.login is assignee
+  if issue_label?
+    issues = _.filter issues, (i) -> _.any(i.labels, (l) -> l.name is issue_label)
+  if query?
+    issues = _.filter issues, (i) ->
+              _.any [i.body, i.title], (text) -> _s.include text.toLowerCase(), query
+  if limit?
+    issues = _.first issues, parseInt limit
+  issues
+
+# Resolve assignee name to a potential GitHub username using sender
+# information and/or environment variables.
+complete_assignee = (msg, name) ->
+  name = msg.message.user.name if name is "my"
+  name = name.replace("@", "")
+  # Try resolving the name to a GitHub username using full, then first name:
   resolve = (n) -> process.env["HUBOT_GITHUB_USER_#{n.replace(/\s/g, '_').toUpperCase()}"]
-  # Try resolving the name to a GitHub username using full, then first name:
-  resolved = resolve(name) or resolve(_s.words(name)[0])
-  if resolved? then resolved else name
+  resolve(name) or resolve(name.split(' ')[0]) or name

-get_repo = (repo) ->
+# Resolve repo to a qualified GitHub repo using environment variables.
+complete_repo = (repo) ->
   repo = process.env.HUBOT_GITHUB_REPO unless repo?
-  repo = repo.replace "for ", ""
   repo = "#{process.env.HUBOT_GITHUB_USER}/#{repo}" unless _s.include repo, "/"
   repo

 module.exports = (robot) ->
-  robot.respond /show (me)?\s*(\d+|\d+ of)?\s*(@?\w+'s|my)?\s*(\S+)?\s*issues\s*(for \S+)?\s*(about .+)?/i, (msg) ->
+  robot.respond ASK_REGEX, (msg) ->
+    criteria = parse_criteria msg.message.text
+    criteria.repo = complete_repo criteria.repo
+    criteria.assignee = complete_assignee msg, criteria.assignee if criteria.assignee?
+
     oauth_token = process.env.HUBOT_GITHUB_TOKEN
-    [limit, assignee, issue_label, repo, query] = msg.match[2..]
-
-    repo = get_repo repo
-
-    msg.http("https://api.github.com/repos/#{repo}/issues")
+    msg.http("https://api.github.com/repos/#{criteria.repo}/issues")
       .headers(Authorization: "token #{oauth_token}", Accept: "application/json")
       .query(state: "open", sort: "created")
       .get() (err, res, body) ->
@@ -44,25 +90,9 @@
           return

         issues = JSON.parse(body)
-
-        if assignee?
-          assignee = msg.message.user.name if assignee is "my"
-          assignee = get_username assignee
-          issues = _.filter issues, (i) -> i.assignee? and i.assignee.login is assignee
-
-        if issue_label?
-          issues = _.filter issues, (i) -> _.any(i.labels, (l) -> l.name is issue_label)
-
-        if query?
-          query = query.replace "about ", ""
-          issues = _.filter issues, (i) ->
-                    _.any [i.body, i.title], (text) -> _s.include text.toLowerCase(), query
-
-        if limit?
-        	limit = parseInt limit.replace " of", ""
-        	issues = _.first issues, limit
-
-        if issues.length == 0
+        issues = filter_issues issues, criteria
+
+        if _.isEmpty issues
             msg.send "No issues found."
         else
           for issue in issues
@@ -70,3 +100,42 @@
             assignee = if issue.assignee then " (#{issue.assignee.login})" else ""
             msg.send "[#{issue.number}] #{issue.title} #{labels}#{assignee} = #{issue.html_url}"

+
+# TODO: Move this once `make test` works again.
+test = ->
+  is_defined = (x)  -> x?
+  one_of     = (xs) -> xs[Math.floor Math.random() * xs.length]
+  maybe      = (x)  -> one_of [x, undefined]
+  pp         = (o)  -> "{ " + ("#{k}: '#{v}'" for k, v of o).join(', ') + " }"
+
+  # Get criteria for random ask string.
+  random_criteria = ->
+    me: (maybe 'me'),
+    limit: (maybe one_of [1..100]),
+    assignee: (maybe one_of ["my", "@fred", "julia_roberts"]),
+    label: (maybe one_of ["ui", "show-stopper"]),
+    repo: (maybe one_of ["github/hubot", "hubot-scripts", "janky"]),
+    query: (maybe one_of ["firefox", "internet explorer"])
+
+  # Create the string for the robot to receive from criteria. We create this
+  # string from randomized criteria, then parse the string and compare the
+  # results to the randomized criteria to test the `parse_criteria` function.
+  show_criteria = ({me, limit, assignee, label, repo, query}) -> [
+    'show', me,
+    limit, 'of' if limit? and assignee?,
+    (if assignee? and assignee isnt "my" then "#{assignee}'s" else assignee),
+    label,
+    'issues',
+    'for' if repo?, repo,
+    'about' if query?, query
+  ].filter(is_defined).join ' '
+
+  # QuickCheck-inspired randomized test cases.
+  _.times 100, ->
+    criteria_expected = random_criteria()
+    ask = show_criteria criteria_expected
+    criteria_parsed = parse_criteria ask
+    unless _.isEqual criteria_expected, criteria_parsed
+      console.log "Counterexample: \"#{ask}\"\n\tExpected: #{pp criteria_expected}\n\tGot:      #{pp criteria_parsed}"
+
+#test()


fixed routing for replace screens

fixed routing for replace screens

--- src/scripts/screenService.coffee
+++ src/scripts/screenService.coffee
@@ -6,6 +6,7 @@

 		replace: (screen, params) ->
 			$location.path "/#{screen}"
+				.replace()
 				.search params
 				.replace()


Set topic on game start

Set topic on game start

--- src/Game.coffee
+++ src/Game.coffee
@@ -57,6 +57,8 @@
         @timer = new Timer('#time', parseInt(data.elapsed/1000))
         @timer.startInterval()

+      @topic = data.topic
+
       $('#word').removeAttr('disabled')
       $('#info').fadeOut 200, =>
           $('#info').html('<strong> Game has started! </strong> Go go go! Topic is '+ @topic+'.').fadeIn(300)


Hack to remove the trailing slash that is being inserted after the query string.

--- public/scripts/refresh.coffee
+++ public/scripts/refresh.coffee
@@ -4,6 +4,7 @@
 refresh = ->
   path = window.location.pathname
   queryString = window.location.search.substring(1)
+  queryString = queryString.replace("/", "")
   $.get path, queryString, (response) ->
     $(".widget").html(response)


cocalc: close X larger on warning banner of free projects

--- src/smc-webapp/project_page.cjsx
+++ src/smc-webapp/project_page.cjsx
@@ -234,8 +234,8 @@
             display    : 'inline-block'
             float      : 'right'
             fontWeight : 700
-            top        : -5
-            fontSize   : 18
+            top        : -4
+            fontSize   : '18pt'
             color      : 'grey'
             position   : 'relative'
             height     : 0


reverted an error message that I forgot to uncomment

--- src/tasks/watch-analytics.coffee
+++ src/tasks/watch-analytics.coffee
@@ -18,8 +18,8 @@
           timeInSeconds = data.readUInt32BE(12)
           value = data.readUInt16BE(16)
           console.log("#{name}: #{timeInSeconds}, #{value}")
-#        else
-#          console.log "failed to parse message #{data.toString()} - expected integer messages to be length 18 but was #{data.length}"
+        else
+          console.log "failed to parse message #{data.toString()} - expected integer messages to be length 18 but was #{data.length}"

       else if format == 'F'
         if data.length == 20


Add touch kinetic scrolling, with non-working edge bounce.

--- src/traits/ScrollableView.coffee
+++ src/traits/ScrollableView.coffee
@@ -89,6 +89,22 @@
 ###
 refHeight = 0

+
+velocity = 0
+
+amplitude = 0
+
+frame = 0
+
+target = 0
+
+scrollTimestamp = null
+
+scrollTicker = null
+
+TICK_INTERVAL = 30
+
+T_CONST = 325

 ###*
 # Start the view scrolling.
@@ -106,6 +122,25 @@
   return

+startKineticScrolling = ->
+  velocity = amplitude = 0
+  frame = offset
+  scrollTimestamp = Date.now()
+  clearInterval scrollTicker
+  scrollTicker = setInterval track, TICK_INTERVAL
+
+
+track = ->
+  now = Date.now()
+  elapsed = now - scrollTimestamp
+  scrollTimestamp = now
+  delta = offset - frame
+  frame = offset
+  v = 1000 * delta / (1 + elapsed)
+  velocity = 0.8 * v + 0.2 * velocity
+
+
+
 ###*
 # Stops view scrolling.
 #
@@ -116,6 +151,28 @@
   scrolling = false

+
+stopKineticScrolling = ->
+  clearInterval scrollTicker
+  if Math.abs(velocity) > 10
+    amplitude = 0.8 * velocity
+    target = Math.round(offset + amplitude)
+    scrollTimestamp = Date.now()
+    requestAnimationFrame => autoScroll.call @
+
+
+autoScroll = ->
+  if amplitude
+    elapsed = Date.now() - scrollTimestamp
+    delta = -amplitude * Math.exp(-elapsed / T_CONST)
+    if Math.abs(delta) > 0.5
+      console.log target, delta, target + delta
+      scroll.call @, target + delta
+      requestAnimationFrame => autoScroll.call @
+    else
+      scroll.call @, target
+
+
 ###*
 # `touchstart` handler starts scrolls initiated by a touch event.
 #
@@ -125,6 +182,7 @@
 ###
 onTouchStart = (e) ->
   startScrolling.call @, e
+  startKineticScrolling.call @
   e.preventDefault()
   e.stopPropagation()
   return
@@ -138,7 +196,8 @@
 # @function onTouchEnd
 ###
 onTouchEnd = (e) ->
-  stopScrolling()
+  stopScrolling.call @
+  stopKineticScrolling.call @
   e.preventDefault()
   e.stopPropagation()
   return


:bug: Clear views after resetting everything else

This prevents a bug where calling `::getView` returns two different instances if
such method is invoked after views are cleared out (e.g. packages deactivation
in `packageManager.reset()`).

--- src/atom-environment.coffee
+++ src/atom-environment.coffee
@@ -238,31 +238,31 @@
     @deserializers.clear()
     @registerDefaultDeserializers()

+    @config.clear()
+    @setConfigSchema()
+
+    @keymaps.clear()
+    @keymaps.loadBundledKeymaps()
+
+    @commands.clear()
+    registerDefaultCommands(this)
+
+    @styles.restoreSnapshot(@initialStyleElements)
+
+    @menu.clear()
+
+    @clipboard.reset()
+
+    @notifications.clear()
+
+    @contextMenu.clear()
+
+    @packages.reset()
+
+    @project.reset(@packages)
+
     @views.clear()
     @registerDefaultViewProviders()
-
-    @config.clear()
-    @setConfigSchema()
-
-    @keymaps.clear()
-    @keymaps.loadBundledKeymaps()
-
-    @commands.clear()
-    registerDefaultCommands(this)
-
-    @styles.restoreSnapshot(@initialStyleElements)
-
-    @menu.clear()
-
-    @clipboard.reset()
-
-    @notifications.clear()
-
-    @contextMenu.clear()
-
-    @packages.reset()
-
-    @project.reset(@packages)

     @state.packageStates = {}


Remove unused package manager shims on atom

--- src/atom.coffee
+++ src/atom.coffee
@@ -141,8 +141,6 @@
   #TODO Remove theses once packages have been migrated
   activatePackage: (args...) -> @packages.activatePackage(args...)
   deactivatePackage: (args...) -> @packages.deactivatePackage(args...)
-  getActivePackage: (args...) -> @packages.getActivePackage(args...)
-  getActivePackages: (args...) -> @packages.getActivePackages(args...)

   loadThemes: ->
     @themes.load()


more debugging

more debugging

--- src/config.iced
+++ src/config.iced
@@ -92,6 +92,7 @@
   #-------------------

   load : (cb) ->
+    log.debug "+ loading config file #{@filename}"
     err = null
     await fs.readFile @filename, defer err, file
     if err?
@@ -109,6 +110,7 @@
           log.error "Missing JSON component '#{key}' in #{@filename}"
           err = new E.ConfigError "missing component '#{key}'"

+    log.debug "- loaded config file -> #{JSON.stringify @json}"
     cb err

   #-------------------


Add iteration tests to Map ut

Add iteration tests to Map ut

--- test/auto/unit/ut-map.coffee
+++ test/auto/unit/ut-map.coffee
@@ -241,4 +241,208 @@

       expect(objectFound).to.be.true

-# TODO add iteration tests here
+  describe 'Iteration:', ->
+    describe 'Copy:', ->
+      describe 'foldl', ->
+        foldl = Iteration.Copy.foldl
+
+        it 'should return a0 iterating through empty map', ->
+          test_map = new Map
+          a = foldl test_map, 4, (a, {x}) ->
+            x - a
+
+          expect(a).to.be.equal 4
+
+        it 'should return correct value iterating through non-empty map', ->
+          test_map = new Map [{key:'1', x: 10}, {key: '2', x: -3}]
+          a = foldl test_map, 4, (a, {x}) ->
+            x - a
+
+          expect(a).to.be.equal -9
+
+      describe 'foldr', ->
+        foldr = Iteration.Copy.foldr
+
+        it 'should return a0 iterating through empty map', ->
+          test_map = new Map
+          a = foldr test_map, 4, ({x}, a) ->
+            x - a
+
+          expect(a).to.be.equal 4
+
+        it 'should return correct value iterating through non-empty map', ->
+          test_map = new Map [{key:'1', x: 10}, {key: '2', x: -3}]
+          a = foldr test_map, 4, ({x}, a) ->
+            x - a
+
+          expect(a).to.be.equal 17
+
+      describe 'map', ->
+        map = Iteration.Copy.map
+
+        it 'should return empty map iterating through empty map', ->
+          test_map = new Map
+          r = map test_map, ({x}) ->
+            -x
+
+          expect(r.toArray()).to.be.eql []
+          expect(r).to.be.not.equal test_map
+
+        it 'should return correct value iterating through non-empty map', ->
+          test_map = new Map [{key:'1', x: 10}, {key: '2', x: -3}]
+          r = map test_map, ({x}) ->
+            -x
+
+          expect(r.toArray()).to.be.eql [{key:'1', x: -10}, {key: '2', x: 3}]
+          expect(r).to.be.not.equal test_map
+
+      describe 'rmap', ->
+        rmap = Iteration.Copy.rmap
+
+        it 'should return empty map iterating through empty map', ->
+          test_map = new Map
+          r = rmap test_map, ({x}) ->
+            -x
+
+          expect(r.toArray()).to.be.eql []
+          expect(r).to.be.not.equal test_map
+
+        it 'should return correct value iterating through non-empty map', ->
+          test_map = new Map [{key:'1', x: 10}, {key: '2', x: -3}]
+          r = rmap test_map, ({x}) ->
+            -x
+
+          expect(r.toArray()).to.be.eql [{key:'2', x: 3}, {key: '1', x: -10}]
+          expect(r).to.be.not.equal test_map
+
+      describe 'forEach', ->
+        forEach = Iteration.Copy.forEach
+
+        it 'should not perform callback iterating through empty map', ->
+          test_map = new Map
+          r = []
+          forEach test_map, ({x}) ->
+            r.push(x)
+
+          expect(r).to.be.eql []
+
+        it 'should perform callback iterating through non-empty map', ->
+          test_map = new Map [{key:'1', x: 10}, {key: '2', x: -3}]
+          r = []
+          forEach test_map, ({key, x}) ->
+            r.push(key)
+            r.push(x)
+
+          expect(r).to.be.eql ['1', 10, '2', -3]
+
+      describe 'filter', ->
+        filter = Iteration.Copy.filter
+
+        it 'should return empty map iterating through empty map', ->
+          test_map = new Map
+          r = filter test_map, ({x}) ->
+            x > 0
+
+          expect(r.toArray()).to.be.eql []
+          expect(r).to.be.not.equal test_map
+
+        it 'should return correct value iterating through non-empty map', ->
+          test_map = new Map [{key:'1', x: 10}, {key: '2', x: -3}]
+          r = filter test_map, ({x}) ->
+            x > 0
+
+          expect(r.toArray()).to.be.eql [{key: '1', x: 10}]
+          expect(r).to.be.not.equal test_map
+
+      describe 'any', ->
+        any = Iteration.Copy.any
+
+        it 'should return false through empty map', ->
+          test_map = new Map
+          p = ({x}) ->
+            x > 0
+
+          expect(any test_map, p).to.be.false
+
+        it 'should return correct value iterating through non-empty map', ->
+          test_map = new Map [{key:'1', x: 10}, {key: '2', x: -3}]
+          p = ({x}) ->
+            x > 0
+
+          expect(any test_map, p).to.be.true
+
+        it 'should stop iterationg once result found', ->
+          test_map = new Map [{key:'1', x: 10}, {key: '2', x: -3}, {key: '3', x: 5}, {key: '4', x: -7}]
+          count = 0
+          p = ({x}) ->
+            ++count
+            x < 0
+
+          expect(any test_map, p).to.be.true
+          expect(count).to.be.equal 2
+
+      describe 'all', ->
+        all = Iteration.Copy.all
+
+        it 'should return false through empty map', ->
+          test_map = new Map
+          p = ({x}) ->
+            x > 0
+
+          expect(all test_map, p).to.be.false
+
+        it 'should return correct value iterating through non-empty map', ->
+          test_map = new Map [{key:'1', x: 10}, {key: '2', x: -3}]
+          p = ({x}) ->
+            x > 0
+
+          expect(all test_map, p).to.be.false
+
+        it 'should stop iterationg once result found', ->
+          test_map = new Map [{key:'1', x: 10}, {key: '2', x: -3}, {key: '3', x: 5}, {key: '4', x: -7}]
+          count = 0
+          p = ({x}) ->
+            ++count
+            x > 0
+
+          expect(all test_map, p).to.be.false
+          expect(count).to.be.equal 2
+
+    describe 'Replace:', ->
+      describe 'map', ->
+        map = Iteration.Replace.map
+
+        it 'should return empty map iterating through empty map', ->
+          test_map = new Map
+          r = map test_map, ({x}) ->
+            -x
+
+          expect(r.toArray()).to.be.eql []
+          expect(r).to.be.equal test_map
+
+        it 'should return correct value iterating through non-empty map', ->
+          test_map = new Map [{key:'1', x: 10}, {key: '2', x: -3}]
+          r = map test_map, ({x}) ->
+            -x
+
+          expect(r.toArray()).to.be.eql [{key: '1', x: -10}, {key: '2', x: 3}]
+          expect(r).to.be.equal test_map
+
+      describe 'filter', ->
+        filter = Iteration.Replace.filter
+
+        it 'should return empty map iterating through empty map', ->
+          test_map = new Map
+          r = filter test_map, ({x}) ->
+            x > 0
+
+          expect(r.toArray()).to.be.eql []
+          expect(r).to.be.equal test_map
+
+        it 'should return correct value iterating through non-empty map', ->
+          test_map = new Map [{key:'1', x: 10}, {key: '2', x: -3}]
+          r = filter test_map, ({x}) ->
+            x > 0
+
+          expect(r.toArray()).to.be.eql [{key: '1', x: 10}]
+          expect(r).to.be.equal test_map


Change max computation

Change max computation

--- app/src/controllers/wallet/send/wallet_send_index_dialog_view_controller.coffee
+++ app/src/controllers/wallet/send/wallet_send_index_dialog_view_controller.coffee
@@ -86,8 +86,7 @@
     total = ledger.Amount.fromSatoshi(0)
     for output in utxo
       total = total.add(output.get('value'))
-    estimatedSize = ledger.bitcoin.estimateTransactionSize(utxo.length, 2).max
-    fees = feePerByte.multiply(estimatedSize)
+    {fees} = @_computeAmount(feePerByte, total)
     amount = total.subtract(fees)
     if amount.lte(0)
       amount = ledger.Amount.fromSatoshi(0)
@@ -191,9 +190,9 @@
   _selectedAccount: ->
     Account.find(index: parseInt(@view.accountsSelect.val())).first()

-  _computeAmount: (feePerByte = ledger.Amount.fromSatoshi(@view.feesSelect.val()).divide(1000)) ->
+  _computeAmount: (feePerByte = ledger.Amount.fromSatoshi(@view.feesSelect.val()).divide(1000), desiredAmount = undefined) ->
     account = @_selectedAccount()
-    desiredAmount = ledger.Amount.fromSatoshi(@_transactionAmount())
+    desiredAmount ?= ledger.Amount.fromSatoshi(@_transactionAmount())
     if desiredAmount.lte(0)
       return total: ledger.Amount.fromSatoshi(0), amount: ledger.Amount.fromSatoshi(0), fees: ledger.Amount.fromSatoshi(0), utxo: [], size: 0
     utxo = @_utxo


fix width/height and x/y range mixups

fix width/height and x/y range mixups

--- static/coffee/common/plot.coffee
+++ static/coffee/common/plot.coffee
@@ -57,12 +57,12 @@
     super(_.defaults(options, @default_options))

     @view_state = new ViewState({
-      canvas_width:  options.canvas_height ? @mget('canvas_height')
-      canvas_height: options.canvas_width  ? @mget('canvas_width')
+      canvas_width:  options.canvas_width  ? @mget('canvas_width')
+      canvas_height: options.canvas_height ? @mget('canvas_height')
       x_offset:      options.x_offset      ? @mget('x_offset')
       y_offset:      options.y_offset      ? @mget('y_offset')
-      outer_width:   options.outer_height  ? @mget('outer_height')
-      outer_height:  options.outer_width   ? @mget('outer_width')
+      outer_width:   options.outer_width   ? @mget('outer_width')
+      outer_height:  options.outer_height  ? @mget('outer_height')
       border_top:    options.border_top    ? @mget('border_top')    ? @mget('border')
       border_bottom: options.border_bottom ? @mget('border_bottom') ? @mget('border')
       border_left:   options.border_left   ? @mget('border_left')   ? @mget('border')
@@ -78,7 +78,7 @@
     })

     @ymapper = new LinearMapper({
-      source_range: @x_range
+      source_range: @y_range
       target_range: @view_state.get('inner_range_vertical')
     })


Fix title of modal

Fix title of modal

--- static/coffee/controllers.coffee
+++ static/coffee/controllers.coffee
@@ -71,7 +71,7 @@

   $scope.onCaseWithoutMessage = () ->
     ModalService.createCase({
-      title: "Open case"
+      title: "Open Case"
     }).then((result) ->
       CaseService.open(null, result.text, result.partner, result.user, result.urn).then((caseObj) ->
         caseUrl = 'case/read/' + caseObj.id + '/'


Update dispatcher.coffee

Update dispatcher.coffee

--- src/chaplin/dispatcher.coffee
+++ src/chaplin/dispatcher.coffee
@@ -6,7 +6,7 @@
 utils = require 'chaplin/lib/utils'
 EventBroker = require 'chaplin/lib/event_broker'

-module.exports = class Dispatcherc
+module.exports = class Dispatcher
   # Borrow the static extend method from Backbone.
   @extend = Backbone.Model.extend


output event name for delegateListeners, for better debug

--- src/chaplin/views/view.coffee
+++ src/chaplin/views/view.coffee
@@ -288,7 +288,7 @@
           method = this[method]
         if typeof method isnt 'function'
           throw new Error 'View#delegateListeners: ' +
-            "#{method} must be function"
+            "listener for \"#{key}\" must be function"

         # Split event name and target.
         [eventName, target] = key.split ' '


Change default member resource to None.

Change default member resource to None.

--- client/landing/app/Applications/Groups.kdapplication/Views/groupcreation.coffee
+++ client/landing/app/Applications/Groups.kdapplication/Views/groupcreation.coffee
@@ -178,7 +178,7 @@
             itemClass     : KDSelectBox
             type          : "select"
             name          : "allocation"
-            defaultValue  : "10"
+            defaultValue  : "0"
             cssClass      : "allocation"
             change        : @bound "allocationChanged"
             selectOptions : [


fixed blur radius on apply

fixed blur radius on apply

--- lib/editor-background.coffee
+++ lib/editor-background.coffee
@@ -765,9 +765,9 @@

       @elements.image = document.createElement 'img'
       @elements.image.id='editor-background-image'
-      @elements.image.setAttribute 'src',conf.image.imageURL
-
-      @elements.blurredImage = conf.image.imageUrl
+      @elements.image.setAttribute 'src',conf.image.url
+
+      @elements.blurredImage = conf.image.url

       @insertTextBackgroundCss()

@@ -898,14 +898,14 @@
       conf = @configWnd.get('editor-background')
       @elements.image.setAttribute 'src',conf.image.url
       applyBlur = false
-      if conf.image.blur > 0
+      if conf.image.blurRadius > 0
         if @elements.image?
           if @elements.image.complete
             applyBlur = true
           else
             setTimeout (=> @blurImage.apply @),1000
       if applyBlur and conf.image.url
-        imageData = blur.stackBlurImage @elements.image, conf.image.blur, false
+        imageData = blur.stackBlurImage @elements.image, conf.image.blurRadius, false
         base64Data = imageData.replace(/^data:image\/png;base64,/, "")
         filename = atom.packages.resolvePackagePath('editor-background')+
         "/blur.png"
@@ -968,6 +968,8 @@
       if conf.image.style
         @elements.plane.style.cssText+=conf.image.style

+      @blurImage()
+
       if conf.other.transparentTabBar
         inline @elements.tabBar,'background:rgba(0,0,0,0) !important;'
         inline @elements.insetPanel,'background:rgba(0,0,0,0) !important;'


Fixed issue #13

Fixed issue #13

--- lib/execution-manager.coffee
+++ lib/execution-manager.coffee
@@ -110,7 +110,7 @@
         @writeDataLine(lastLine) if lastLine
         @activeDataWriter.dispose()
         @process.stdout.resume()
-      else
+      else if lines.length > 1
         @writeDataLine(lines.shift())
         @terminal.newLine()
     ,10


stray log

stray log

--- lib/middleware/locals.coffee
+++ lib/middleware/locals.coffee
@@ -30,7 +30,6 @@
   res.locals.sd.CURRENT_PATH = parse(req.url).pathname
   res.locals.sd.ARTSY_XAPP_TOKEN = artsyXapp.token
   res.locals.sd.REFERRER = referrer = req.get 'Referrer'
-  console.log referrer, 'moo'
   res.locals.sd.MEDIUM = new Referrer(referrer).medium if referrer
   res.locals.sd.EIGEN = req.headers?['user-agent']?.match('Artsy-Mobile')?
   res.locals.sd.REQUEST_TIMESTAMP = Date.now()


Moving exports up

Moving exports up

--- framer/SVG.coffee
+++ framer/SVG.coffee
@@ -1,7 +1,7 @@
 {_} = require "./Underscore"
 {Color} = require "./Color"

-class SVG
+class exports.SVG

 	@validFill = (value) ->
 		Color.validColorValue(value) or _.startsWith(value, "url(")
@@ -60,5 +60,3 @@

 		return {targets, children}

-
-exports.SVG = SVG


Scope keybindings

Scope keybindings

Scope keybinding to html editors.

--- keymaps/atom-html-preview.cson
+++ keymaps/atom-html-preview.cson
@@ -1,2 +1,2 @@
-'atom-workspace':
+'atom-text-editor[data-grammar~=html]':
   'ctrl-shift-h': 'atom-html-preview:toggle'


Go back to the previous column

Go back to the previous column

--- lib/ide-haskell-hasktags.coffee
+++ lib/ide-haskell-hasktags.coffee
@@ -24,6 +24,7 @@
     @stack.push
       uri: editor.getURI()
       line: editor.getLastCursor().getBufferRow()
+      column: editor.getLastCursor().getBufferColumn()
     atom.workspace.open tag.uri,
       initialLine: tag.line
       searchAllPanes: true
@@ -40,6 +41,7 @@
         if (prevpos = @stack.pop())?
           atom.workspace.open prevpos.uri,
             initialLine: prevpos.line
+            initialColumn: prevpos.column
             searchAllPanes: true
     @disposables.add atom.commands.add 'atom-text-editor',
       'ide-haskell-hasktags:show-file-tags': ({currentTarget}) =>


Change name of deviationsRawData method

Change name of deviationsRawData method

New name is deviationsrawdata to be consistent with the api endpoints

--- lib/services/disturbance.coffee
+++ lib/services/disturbance.coffee
@@ -13,6 +13,6 @@
   {
     deviations: (args...) ->
       service.prepareRequest 'deviations', args...
-    deviationsRawData: (args...) ->
+    deviationsrawdata: (args...) ->
       service.prepareRequest 'deviationsrawdata', args...
   }


:lipstick: Correct indent

:lipstick: Correct indent

--- src/token.coffee
+++ src/token.coffee
@@ -136,7 +136,7 @@
       classes += ' indent-guide' if hasIndentGuide
       classes += ' invisible-character' if invisibles.tab
       value = if invisible.tab then @value.replace(StartCharacterRegex, invisibles.tab) else @value
-    html = "#{@escapeString(value)}"
+      html = "#{@escapeString(value)}"
     else
       startIndex = 0
       endIndex = @value.length


Added missing `()`

Added missing `()`

--- src/components/Group.coffee
+++ src/components/Group.coffee
@@ -13,7 +13,7 @@
     @inPorts.in.on 'data', (data) =>
       @outPorts.out.beginGroup group for group in groups
       @outPorts.out.send data
-      @outPorts.out.endGroup for group in groups
+      @outPorts.out.endGroup() for group in groups

     @inPorts.group.on 'data', (data) =>
       groups.push data


Remove setGuessFunctionName

Remove setGuessFunctionName

--- test/unit/client_test.coffee
+++ test/unit/client_test.coffee
@@ -21,16 +21,6 @@
     client = new Client()
     client.setProject("[custom_project_id]", "[custom_key]")
     expect(client.getProject()).to.deep.equal([ "[custom_project_id]", "[custom_key]" ])
-
-  describe "guessFunctionName", ->
-    it "is false by default", ->
-      client = new Client();
-      expect(client.getGuessFunctionName()).to.be.false
-
-    it "can set and read", ->
-      client = new Client()
-      client.setGuessFunctionName(true)
-      expect(client.getGuessFunctionName()).to.be.true

   describe "outputFormat", ->
     it "is JSON by default", ->


remove space

remove space

--- lib/project-find-view.coffee
+++ lib/project-find-view.coffee
@@ -374,4 +374,4 @@
     @search(onlyRunIfActive: true, caseSensitive: not @model.getFindOptions().caseSensitive)

   toggleWholeWordOption: ->
-    @search(onlyRunIfActive: true, wholeWord: not @model.getFindOptions().wholeWord)
+    @search(onlyRunIfActive: true, wholeWord: not @model.getFindOptions().wholeWord)


use _.cached for caching event values too

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -212,10 +212,7 @@
 class Next extends Event
   constructor: (valueF, sourceEvent) ->
     if isFunction(valueF)
-      @value = =>
-        v = valueF()
-        @value = _.always(v)
-        v
+      @value = _.cached(valueF)
     else
       @value = _.always(valueF)
   isNext: -> true


implement Property.scan

implement Property.scan

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -603,6 +603,26 @@
       combine(sampler, nop, pushPropertyValue).changes().takeUntil(sampler.filter(false).mapEnd())
   sample: (interval) =>
     @sampledBy Bacon.interval(interval, {})
+  scan: (seed, f) =>
+    f = toCombinator(f)
+    acc = seed
+    @withSubscribe (sink) =>
+      initSent = false
+      unsub = @subscribe (event) =>
+        if (event.hasValue())
+          if (initSent && event.isInitial())
+            Bacon.more # init already sent, skip this one
+          else
+            if (event.isInitial())
+              initSent = true
+            acc = f(acc, event.value)
+            sink (event.apply(acc))
+        else
+          sink event
+      if !initSent
+        sink initial(acc)
+      unsub
+
   changes: => new EventStream (sink) =>
     @subscribe (event) =>
       sink event unless event.isInitial()


See #23. Disable EZTV now that it has been shutdown.

--- server/providers/index.coffee
+++ server/providers/index.coffee
@@ -5,7 +5,7 @@

 module.exports = class Providers extends BaseProvider
     allProviders: [
-      new EZTV()
+      #new EZTV()
       new Kickass()
     ]
     search: (query, options, callback) ->


Remove log

Remove log

--- lib/text-manipulation.coffee
+++ lib/text-manipulation.coffee
@@ -47,7 +47,7 @@
       startClose = rangesToCloseForStart.bracketIx
       endOpen = rangesToOpenForEnd.bracketIx
       endClose = rangesToCloseForEnd.bracketIx
-      console.log startOpen, startClose, endOpen, endClose
+
       rangesToOpenAndClose = switch
         when (startOpen == endOpen) and (startClose == endClose) or
              (startOpen < endOpen)  and (startClose >  endClose) then [rangesToOpenForStart, rangesToCloseForStart]


Don't trim queried content

Don't trim queried content

--- lib/view/input-editor.coffee
+++ lib/view/input-editor.coffee
@@ -23,10 +23,6 @@
             @initialize()
             new Promise (resolve, reject) =>
                 @$once 'confirm', (expr) =>
-                    expr = expr
-                        .trim()
-                        .replace(/\\/g, '\\\\')
-                        .replace(/\"/g, '\\"')
                     resolve expr
                 @$once 'cancel', =>
                     reject new QueryCancelledError
@@ -35,8 +31,12 @@
     ready: ->
         # event clusterfuck
         confirmDisposable = atom.commands.add @$el, 'core:confirm', =>
-            @$emit 'confirm', @$el.getModel().getText().trim()     # to local
-            @$dispatch 'input-editor:confirm', @$el.getModel().getText().trim() # to parent
+            expr = @$el.getModel().getText()
+                        .replace(/\\/g, '\\\\')
+                        .replace(/\"/g, '\\"')
+                        .replace(/\n/g, '\\n')
+            @$emit 'confirm', expr     # to local
+            @$dispatch 'input-editor:confirm', expr # to parent
         cancelDisposable = atom.commands.add @$el, 'core:cancel', =>
             @$emit 'cancel'
             @$dispatch 'input-editor:cancel'


Add a test for when the user can't be found.

--- services/track-changes/test/unit/coffee/WebApiManager/WebApiManagerTests.coffee
+++ services/track-changes/test/unit/coffee/WebApiManager/WebApiManagerTests.coffee
@@ -72,6 +72,16 @@
 					.calledWith(new Error("web returned failure status code: 500"))
 					.should.equal true

+		describe "when the user cannot be found", ->
+			beforeEach ->
+				@request.get = sinon.stub().callsArgWith(1, null, {statusCode: 404}, "nothing")
+				@WebApiManager.getUserInfo @user_id, @callback
+
+			it "should return a null value", ->
+				@callback
+					.calledWith(null, null)
+					.should.equal true
+

 	describe "getProjectDetails", ->
 		describe "successfully", ->


- [grunt] temporary disabled `newer:` for sprites and icons task due to bug

Now sprites and icons will properly rebuild on any changes

--- tasks/grunt-contrib-watch.coffee
+++ tasks/grunt-contrib-watch.coffee
@@ -6,17 +6,22 @@
 module.exports = ->
   @config 'watch',
     scss:
-      files: ['<%= path.source.styles %>/{,**/}*.scss','<%= path.temp.styles %>/{,**/}*.scss']
+      files: [
+        '<%= path.source.styles %>/{,**/}*.scss'
+        '<%= path.temp.styles %>/{,**/}*.scss'
+      ]
       tasks: [
         'sass'
         'autoprefixer'
       ]
     icon:
       files: ['<%= path.source.icons %>/{,**/}*.svg']
-      tasks: ['newer:webfont']
+      # @todo Add `newer:` when relative `grunt-newer` bug will be fixed
+      tasks: ['webfont']
     sprite:
       files: ['<%= path.source.sprites %>/{,**/}*.{jpg,jpeg,gif,png}']
-      tasks: ['newer:sprite']
+      # @todo Add `newer:` when relative `grunt-newer` bug will be fixed
+      tasks: ['sprite']
     nunjucks:
       files: ['<%= path.source.layouts %>/{,**/}*.nj']
       tasks: ['newer:nunjucks']


perf(server): start the server via require instead of using node's spawn function

--- tasks/server/spawn-server.coffee
+++ tasks/server/spawn-server.coffee
@@ -1,15 +1,12 @@
 module.exports = (gulp, config) ->
 	q            = require 'q'
-	spawn        = require('child_process').spawn
-	rbServerFile = config.dist.rb.server.scripts.path
+	path         = require 'path'
+	rbServerFile = path.join config.app.dir, config.dist.rb.server.scripts.path

 	# register task
 	# =============
 	gulp.task "#{config.rb.prefix.task}spawn-server", ->
-		defer  = q.defer()
-		server = spawn 'node', [ rbServerFile ]
-		server.stdout.on 'data', (data) ->
-			msg = data.toString().trim()
-			console.log msg.yellow
-			defer.resolve() if msg.indexOf(config.server.msg.start) isnt -1
-		defer.promise
+		defer = q.defer()
+		require rbServerFile
+		defer.resolve()
+		defer.promise


Doing compilation step by step, with tokenize and parse events, and without exec

--- src/beans.coffee
+++ src/beans.coffee
@@ -1,3 +1,4 @@
+coffee   = require 'coffee-script'
 fs       = require 'fs'
 glob     = require 'glob'
 nodeunit = require 'nodeunit'
@@ -17,7 +18,9 @@
   browserRootModule: null
   copyrightFrom: (new Date).getFullYear()
   license: ''
+  onCompile: null
   sourcePath: 'src'
+  targetPath: 'lib'

 # Load package information from multiple sources.
 loadInfo = ->
@@ -29,6 +32,14 @@
   for key of defaults
     info[key] = defaults[key] unless info[key]?

+  # Load compilation event handler, if any.
+  if info.onCompile?
+    info.onCompile = require info.onCompile
+
+  # Expands source and target paths.
+  info.sourcePath = path.resolve info.sourcePath
+  info.targetPath = path.resolve info.targetPath
+
   # Load package.json and override existing significant values.
   package = JSON.parse(fs.readFileSync 'package.json')
   for key in ['author', 'name', 'description', 'version']
@@ -95,8 +106,15 @@
     fn?()

 # Safely make a directory with default permissions.
+# If its parents in path are missing, they are constructed as well.
 makeDir = (dir) ->
-  fs.mkdirSync dir, 0755 unless path.existsSync dir
+  missing = []
+  dir = path.resolve dir
+  until path.existsSync dir
+    missing.unshift dir
+    dir = path.dirname dir
+  for dir in missing
+    fs.mkdirSync dir, 0755

 # Find files based on a global pattern.
 # Call the provided function with the result, if any files are found.
@@ -135,13 +153,37 @@
         if curr.mtime.getTime() isnt prev.mtime.getTime()
           fn file

+# Return current time formatted as HH:mm:ss.
+
+# Compile one CoffeeScript file.
+# Existing event handlers are synchronously invoked in the process.
+# Each event handler is passed a subject and a context (source file).
+compile = (info, file) ->
+  file = fs.realpathSync file
+  src = fs.readFileSync file, 'utf8'
+  try
+    src = coffee.compile src, bare: true
+  catch err
+    err.message = "In #{file}, #{err.message}"
+    throw err
+  source = file.substr(info.sourcePath.length)
+  target = info.targetPath + source.replace(/.coffee$/, '.js')
+  makeDir path.dirname(target)
+  fs.writeFileSync target, src
+  info.onCompile?(target, src)
+  ts = (new Date()).toLocaleTimeString()
+  source = info.sourcePath.substr(path.resolve('.').length + 1) + source
+  console.log ts + ' - compiled ' + source
+
 # Compile all CoffeeScript sources for Node.
 buildNode = (info, watch, fn) ->
-  ifInstalled 'coffee', ->
-    rmrf 'lib', ->
-      src = info.sourcePath
-      tryExec 'coffee', "-cb#{if watch then 'w' else ''} -o lib #{src}", ->
-        fn?()
+  rmrf info.targetPath, ->
+    withFiles path.join(info.sourcePath, '**/*.coffee'), (files) ->
+      for file in files
+        compile info, file
+      if watch
+        watchFiles files, (file) ->
+          compile info, file

 # Use Stitch to create a browser bundle.
 bundle = (info) ->