cleaner replace

cleaner replace

--- src/xml.coffee
+++ src/xml.coffee
@@ -17,6 +17,43 @@
     return [name, attrs, children, opts]

+connect_tags = (parent, child) ->
+    listeners = {}
+    pipe = (event) ->
+        child.on? event, listeners[event] = ->
+            parent.emit(event, arguments...)
+    wire = ->
+        pipe event for event in EVENTS
+    dispose = ->
+        for event in EVENTS
+            if (listener = listeners[event])?
+                child.removeListener?(event, listener)
+                listeners[event] = undefined
+    remove = ->
+        dispose()
+        if this is child
+            parent.removeListener('removed', remove)
+        else
+            child.removeListener('removed', remove)
+        parent.removeListener('replaced', replace)
+        child.removeListener('replaced', replace)
+    replace = (tag) ->
+        if this is child
+            remove.call(parent)
+            child = tag
+            wire()
+        else
+            parent.removeListener('removed', remove)
+            parent = tag
+        tag.once('replaced', replace)
+        tag.once('removed', remove)
+    wire() # add
+    child.once('removed', remove)
+    parent.once('removed', remove)
+    child.once('replaced', replace)
+    parent.once('replaced', replace)
+
+
 add_tag = (newtag, callback) ->
     return callback?.call(this) unless newtag?
     # only when the builder approves the new tag we can proceed with announcing
@@ -25,31 +62,7 @@
         tag.builder ?= @builder
         tag.parent  ?= this

-        self = this
-        listeners = {}
-        pipe = (event) ->
-            tag.on? event, listeners[event] = ->
-                self.emit event, arguments...
-        dispose = (el) ->
-            return unless el is self or el is tag
-            self.removeListener('repipe', repipe)
-            self.removeListener('remove', dispose)
-            tag.removeListener?('remove', dispose)
-            for event in EVENTS
-                if (listener = listeners[event])?
-                    tag.removeListener?(event, listener)
-            listeners = {}
-        wire = ->
-            pipe event for event in EVENTS
-            tag.once?('remove', dispose)
-            self.once('remove', dispose)
-            self.once('repipe', repipe)
-        repipe = (replacement) ->
-            dispose(self)
-            self = replacement
-            wire()
-        # enough preparation, lets do it:
-        wire()
+        connect_tags(this, tag)

         @emit 'add', this, tag
         @emit 'new', tag
@@ -235,20 +248,20 @@
     replace: (rawtag) =>
         tag = @builder?.query 'tag', this, rawtag
         tag = rawtag unless tag? or @builder?
-        # repipe all events to the new tag
-        events = Object.keys(@_events ? {}).map((e) => [e,@listeners(e)])
-        for [event, listeners] in events
-            continue if event is 'newListener' or event is 'maxListeners'
-            for listener in listeners ? []
-                tag.on(event, listener) if typeof listener is 'function'
-        @emit 'repipe', tag
+        return this if this is tag
         @emit 'replace', this, tag
-        @removeAllListeners()
-        this
+        tag.parent  ?= @parent
+        tag.builder ?= @builder
+        if @builder is tag.builder
+            @builder = null
+        @parent = null
+        @emit 'replaced', tag # internal
+        tag

     remove: () =>
         @closed = 'removed' unless @closed
         @emit 'remove', this
+        @emit 'removed' # internal
         @removeAllListeners()
         this


Added macClick.

Added macClick.

--- src/directives/events.coffee
+++ src/directives/events.coffee
@@ -18,6 +18,34 @@
           scope.$apply -> expression scope, $event: event
           true
     ]
+
+#
+# @type directive
+# @name macClick
+# @description
+# macClick recurive go up parent scope to call function
+#
+# @attributes
+# - mac-click:       expression to evaluate upon click
+# - mac-click-depth: Max parent scope depth it should go (default 2)
+#
+angular.module("Mac").directive "macClick", ["$parse", ($parse) ->
+  link: ($scope, element, attr) ->
+    fn    = $parse attr.macClick
+    depth = +(attr.macClickDepth or 2)
+
+    clickAction = (scope, depth) ->
+      return false if depth is 0
+      ret    = fn scope, {$event: event}
+      parent = scope.$parent
+      if not ret and parent?
+        return clickAction(parent, depth - 1)
+      else
+        return true
+
+    element.bind "click", (event) ->
+      $scope.$apply -> clickAction $scope, depth
+]

 #
 # @type directive


Add error handling in LEFT and LEFTB

Add error handling in LEFT and LEFTB

--- src/formula_evaluator.coffee
+++ src/formula_evaluator.coffee
@@ -498,7 +498,8 @@
     len = 1
     if args.length == 2
       len = @expectInteger(args[1])
-
+      if len < 0
+        @error(ERROR_VALUE)
     str.substr(0, len)

   LEFTB: (args)->
@@ -507,6 +508,8 @@
     len = 1
     if args.length == 2
       len = @expectInteger(args[1])
+      if len < 0
+        @error(ERROR_VALUE)

     multibyteSubstr(str, 0, len)


Add translation functions to normalize urls

--- src/helpers/analytics.coffee
+++ src/helpers/analytics.coffee
@@ -1,31 +1,64 @@
 _ = require 'underscore'
 Router = require 'react-router'
 {HistoryLocation} = require 'react-router'
+DestinationHelper = require '../helpers/routes-and-destinations'
+
+# generate custom event data for routes
+Events =
+  viewTaskStep: ({courseId}) ->
+    # track all work done on a course
+    Analytics.sendEvent 'Course', 'Work', label: courseId
+
+  viewStudentDashboard: ({courseId}) ->
+    # compare activity between courses
+    Analytics.sendEvent 'Student', 'Dashboard', label: courseId
+
+
+# Translators convert a url like '/foo/bar/123/baz/1' into a simplified one like just '/foo/bar'
+Translators =
+
+  dashboard:            ({courseId}) -> "/student/choose-course/#{courseId}"
+  viewTaskStep:         ({courseId}) -> "/student/task-step/#{courseId}"
+  viewGuide:            ({courseId}) -> "/student/performance-forecast/#{courseId}"
+  viewStudentDashboard: ({courseId}) -> "/student/dashboard/#{courseId}"
+  viewPractice:         ({courseId}) -> "/student/practice/#{courseId}"
+
+  calendarByDate:        ({courseId}) -> "/teacher/calendar/#{courseId}"
+  viewTeacherGuide:      ({courseId}) -> "/teacher/performance-forecast/#{courseId}"
+  viewScores:            ({courseId}) -> "/teacher/student-scores/#{courseId}"
+  courseSettings:        ({courseId}) -> "/teacher/roster/#{courseId}"
+  editReading:           ({courseId}) -> "/teacher/assignment/edit/#{courseId}"
+  editHomework:          ({courseId}) -> "/teacher/assignment/edit/#{courseId}"
+  editExternal:          ({courseId}) -> "/teacher/assignment/edit/#{courseId}"
+  createReading:         ({courseId}) -> "/teacher/assignment/create/#{courseId}"
+  createHomework:        ({courseId}) -> "/teacher/assignment/create/#{courseId}"
+  createExternal:        ({courseId}) -> "/teacher/assignment/create/#{courseId}"
+  calendarViewPlanStats: ({courseId}) -> "/teacher/metrics/quick/#{courseId}"
+  reviewTask:            ({courseId}) -> "/teacher/metrics/review/#{courseId}"
+
+GA = undefined

 Analytics =

-  onNavigation: (router) ->
-    # Tell GA that all events after this should be credited to this path
-    ga('set', 'page', router.getCurrentPath())
+  setTracker: (tracker) -> GA = tracker

-    route = _.last router.getCurrentRoutes()
-    @handlers[route.name]?( router.getCurrentParams() )
+  onNavigation: (change, router) ->
+    route  = DestinationHelper.routeFromPath(change.path, router.match)
+    return unless GA and route

-    ga('send', 'pageview')
+    params = router.getCurrentParams()
+    path   = Translators[route.name]?( params ) or change.path

+    # if we're also going to send custom events then we set the page
+    if Events[route.name]
+      GA('set', 'page', path)
+      Events[route.name]( params )
+      GA('send', 'pageview') # path's not needed since it was set before events
+    else
+      GA('send', 'pageview', path)

-  sendEvent: (attrs) ->
-    ga('send', 'event', attrs.category, attrs.action, attrs.label, attrs.value)
-
-  handlers:
-    viewTaskStep: ({courseId, id}) ->
-      Analytics.sendEvent( category: 'Task', action: 'Step' )
-
-    viewGuide: ->
-      Analytics.sendEvent( category: 'LearningGuide' )
-
-    viewStudentDashboard: ->
-      Analytics.sendEvent( category: 'Dashboard' )
-
+  sendEvent: (category, action, attrs) ->
+    return unless GA
+    GA('send', 'event', category, action, attrs.label, attrs.value)

 module.exports = Analytics


witch to better position calc for scrubbing

--- src/javascripts/utils.coffee
+++ src/javascripts/utils.coffee
@@ -41,6 +41,9 @@
     number

   @calculateCursorPosition: (event) ->
-    event.pageX - event.target.offsetLeft
+    if (event.offsetX == undefined)
+      event.layerX - event.currentTarget.offsetLeft
+    else
+      event.offsetX

 module.exports = Utils


Optimize tip's mouse-move handling.

Optimize tip's mouse-move handling.

Use RAF when possible, otherwise just throttle to
60fps.

--- src/js/jquery.hlf.tip.coffee
+++ src/js/jquery.hlf.tip.coffee
@@ -146,7 +146,13 @@
         @_onTriggerMouseMove evt
       $t.on @evt('truemouseleave'), (evt) => @sleepByTrigger $t
       if @doFollow is on
-        $t.on 'mousemove', @_onTriggerMouseMove
+        if window.requestAnimationFrame?
+          onMouseMove = (evt) =>
+            requestAnimationFrame (timestamp) =>
+              @_onTriggerMouseMove evt
+        else
+          onMouseMove = _.throttle @_onTriggerMouseMove, 16
+        $t.on 'mousemove', onMouseMove

     # - Bind to the tip on hover so the toggling makes an exception.
     _bind: () ->


this. -> @

this. -> @

--- src/keymap-extensions.coffee
+++ src/keymap-extensions.coffee
@@ -41,7 +41,7 @@
       atom.notifications.addFatalError(error.message, {detail, stack, dismissable: true})

 KeymapManager::subscribeToFileReadFailure = ->
-  this.onDidFailToReadFile (error) =>
+  @onDidFailToReadFile (error) =>
     userKeymapPath = @getUserKeymapPath()
     message = "Failed to load `#{userKeymapPath}`"


replace q in many tests, use catch(done)

--- server/test/integration/api_page.coffee
+++ server/test/integration/api_page.coffee
@@ -1,6 +1,7 @@
 assert = require('chai').assert
 helpers = require '../helpers'
 request = require('request')
+Promise = require('bluebird')
 url = require('url')
 _ = require('underscore')
 async = require('async')
@@ -18,13 +19,12 @@
 test('GET show', (done) ->
   data =
     title: "new page"
-
-  helpers.createPage().
-    then( (page) ->
-      request.get({
+  helpers.createPage().
+    then( (page) ->
+      Promise.promisify(request.get, request)({
         url: helpers.appurl("api/pages/#{page.id}")
         json: true
-      }, (err, res, body) ->
+      }).spread( (res, body) ->
         assert.equal res.statusCode, 200

         reloadedPage = body
@@ -33,19 +33,16 @@

         done()
       )
-    ).catch( (err) ->
-      console.error err
-      throw new Error(err)
-    )
+    ).catch(done)
 )

 test('GET index', (done) ->
   helpers.createPage().
     then( (page) ->
-      request.get({
+      Promise.promisify(request.get, request)({
         url: helpers.appurl("api/pages")
         json: true
-      }, (err, res, body) ->
+      }).spread( (res, body) ->
         assert.equal res.statusCode, 200

         pages = body
@@ -54,10 +51,7 @@

         done()
       )
-    ).catch( (err) ->
-      console.error err
-      throw new Error(err)
-    )
+    ).catch(done)
 )

 test('DELETE page', (done) ->
@@ -75,10 +69,7 @@
             done()
         )
       )
-    ).catch( (err) ->
-      console.error err
-      throw new Error(err)
-    )
+    ).catch(done)
 )

 test('PUT page', (done) ->
@@ -104,10 +95,7 @@
             )
         )
       )
-    ).catch( (err) ->
-      console.error err
-      throw new Error(err)
-    )
+    ).catch(done)
 )

 test('PUT page succeeds with an _id sent', (done) ->
@@ -132,10 +120,7 @@
             done()
         )
       )
-    ).catch( (err) ->
-      console.error err
-      throw new Error(err)
-    )
+    ).catch(done)
 )

 test('PUT page succeeds with an _id sent', (done) ->
@@ -160,10 +145,7 @@
             done()
           )
       )
-    ).catch( (err) ->
-      console.error err
-      throw new Error(err)
-    )
+    ).catch(done)
 )

 test('PUT nesting a section in a page with existing sections', (done) ->
@@ -189,10 +171,7 @@

         done()
       )
-    ).catch( (err) ->
-      console.error err
-      throw new Error(err)
-    )
+    ).catch(done)

   async.series([helpers.createSection, helpers.createSection], createPageWithSection)
 )
@@ -255,25 +234,20 @@
           assert.equal page.title, data.title
           done()
         )
-    catch e
-      done(e)
-
-  ).catch((err) ->
-    console.error err
-    throw err
-  )
+    catch err
+      done(err)
+
+  ).catch(done)
 )

 test('POST create - nesting a section in a page fails when not authenticated', (done) ->
   data = theIndicator = null

   helpers.createUser().then((user) ->
-    Q.nfcall(
-      helpers.createIndicator, {
-        name: 'dat indicator'
-        owner: user
-      }
-    )
+    Promise.promisify(helpers.createIndicator, helpers)({
+      name: 'dat indicator'
+      owner: user
+    })
   ).then( (indicator) ->

     theIndicator = indicator
@@ -288,14 +262,11 @@
         indicator: indicator._id
       }]

-    Q.nfcall(
-      request.post, {
-        url: helpers.appurl('api/pages/')
-        json: true
-        body: data
-      }
-    )
-
+    Promise.promisify(request.post, request)({
+      url: helpers.appurl('api/pages/')
+      json: true
+      body: data
+    })
   ).spread( (res, body) ->

     # Assert expected outcomes
@@ -304,10 +275,7 @@
     assert.equal res.statusCode, 401

     done()
-  ).catch((err) ->
-    console.error err
-    throw err
-  )
+  ).catch(done)
 )

 test("PUT update when given a ID which doesn't exist throws an appropriate error", (done)->


Fixes #3 - filter out jasmine runner  templates from task list

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -94,7 +94,7 @@
               branches: 80
               functions: 80

-  require("matchdep").filterDev("grunt-*").forEach grunt.loadNpmTasks
+  require("matchdep").filterDev("grunt-!(template)*").forEach grunt.loadNpmTasks

   grunt.registerTask "server", "Start a web server to host the app.",
     ["connect", "watch"]


Expose runtimes for require

Expose runtimes for require

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -9,6 +9,10 @@
     noflo_browser:
       everything:
         options:
+          exposed_modules:
+            'noflo': 'noflo'
+            'noflo-runtime-iframe': 'noflo-runtime-iframe'
+            'noflo-runtime-webrtc': 'noflo-runtime-webrtc'
           debug: true
           webpack:
             externals:
@@ -16,7 +20,6 @@
               'module': 'commonjs module' # somewhere inside coffee-script
               'child_process': 'commonjs child_process' # somewhere inside coffee-script
               'jison': 'commonjs jison'
-              'URIjs': 'commonjs urijs'
               'canvas': 'commonjs canvas'
               'mimetype': 'commonjs mimetype'
               'should': 'commonjs should' # used by tests in octo
@@ -32,6 +35,8 @@
               ]
             resolve:
               extensions: ["", ".coffee", ".js"]
+              alias:
+                URIjs: 'urijs'
             node:
               fs: "empty"
           heads: [


minification modifications

minification modifications

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -6,14 +6,20 @@
     requirejs:
 	    compile:
 	      options:
+	        baseUrl: "src"
 	        mainConfigFile: 'src/main.js'
-	        baseUrl: "src"
 	        name: "main"
 	        out: 'src/app.min.js'
-	        useStrict: true #unknown
-	        wrap: true #unknown
-	        inlineText: true #unknown
-	        stubModules: ['text'] #unknown
+	        useStrict: false
+	        wrap: false
+	        inlineText: true
+	        stubModules: ['text']
+	        optimize: "uglify2"
+	        optimizeAllPluginResources: true
+	        findNestedDependencies: true,
+	        paths:
+	         'text': '../assets/lib/requirejs-text/text' # relative to baseUrl
+

   # Load the plugin that provides the "uglify" task.
   #grunt.loadNpmTasks('grunt-contrib-uglify');


[IDA-380] Update tests to not require a server

[rev: matthew.gordon]

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -12,7 +12,6 @@
   specs = 'test/js/spec/**/*.js'
   styles = 'bower_components/hp-autonomy-js-testing-utils/src/css/bootstrap-stub.css'
   serverPort = 8000
-  host = "http://localhost:#{serverPort}/"
   helpers = 'bower_components/hp-autonomy-js-testing-utils/src/js/jasmine-custom-matcher.js'

   grunt.initConfig
@@ -28,13 +27,13 @@
       server:
         options:
           port: serverPort
+          useAvailablePort: true
     jasmine:
       test:
         src: sourcePath
         options:
           helpers: helpers
-          host: host
-          keepRunner: true
+          keepRunner: false
           outfile: jasmineSpecRunner
           specs: specs
           styles: styles
@@ -45,8 +44,7 @@
         src: sourcePath
         options:
           helpers: helpers
-          host: host
-          keepRunner: true
+          keepRunner: false
           outfile: coverageSpecRunner
           specs: specs
           styles: styles
@@ -164,10 +162,10 @@
   grunt.loadNpmTasks 'grunt-gh-pages'
   grunt.loadNpmTasks 'grunt-jsdoc'

-  grunt.registerTask 'default', ['lint', 'connect:server', 'jasmine:test', 'jasmine:coverage']
-  grunt.registerTask 'test', ['connect:server', 'jasmine:test']
-  grunt.registerTask 'browser-test', ['connect:server:keepalive']
-  grunt.registerTask 'coverage', ['connect:server', 'jasmine:coverage']
+  grunt.registerTask 'default', ['lint', 'jasmine:test', 'jasmine:coverage']
+  grunt.registerTask 'test', ['jasmine:test']
+  grunt.registerTask 'browser-test', ['jasmine:test:build', 'connect:server:keepalive']
+  grunt.registerTask 'coverage', ['jasmine:coverage']
   grunt.registerTask 'lint', ['jshint', 'coffeelint']
   grunt.registerTask 'doc', ['jsdoc']
   grunt.registerTask 'push-doc', ['doc', 'gh-pages:default']


Disable coffelint, currently failing

Disable coffelint, currently failing

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -82,7 +82,7 @@
       @task.run 'uglify'

   @registerTask 'test', 'Build NoFlo and run automated tests', (target = 'all') =>
-    @task.run 'coffeelint'
+#    @task.run 'coffeelint'
     @task.run 'coffee'
     @task.run 'noflo_manifest'
     if target is 'all' or target is 'nodejs'


Change the way tests run

Change the way tests run

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -81,12 +81,13 @@

   @registerTask 'test', 'Build NoFlo and run automated tests', (target = 'all') =>
     @task.run 'coffeelint'
-    @task.run 'build'
-    @task.run 'connect'
+    @task.run 'coffee'
+    @task.run 'noflo_manifest'
     if target is 'all' or target is 'nodejs'
       @task.run 'mochaTest'
     if target is 'browser'
-      @task.run 'coffee'
+      @task.run 'connect'
+      @task.run 'noflo_browser'
       @task.run 'mocha_phantomjs'

   @registerTask 'default', ['test']


Removing Chart.js from the grunt

Removing Chart.js from the grunt

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -17,7 +17,6 @@
         'app/js/checksley.js',
         'app/js/q.js',
         'app/js/sha1.js',
-        'app/js/Chart.js',
         'app/js/excanvas.js',
         'app/js/jquery.flot.js',
         'app/js/jquery.flot.pie.js',


Don't rely on linux features of less than a year old due to outdated servers.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -130,9 +130,9 @@
           'build/server/nu.min.js':                          ['build/nu.js']
     shell:
       gzip_js_files:
-        command: 'find build/ -iname \'*.js\'  -print0 | xargs -0 -n 1 gzip -kf -9'
+        command: ' find build/ -iname \'*.js\'  -exec bash -c \' gzip -9 -f < "{}" > "{}.gz" \' \\; '
       gzip_css_files:
-        command: 'find build/ -iname \'*.css\'  -print0 | xargs -0 -n 1 gzip -kf -9'
+        command: ' find build/ -iname \'*.css\'  -exec bash -c \' gzip -9 -f < "{}" > "{}.gz" \' \\; '

     copy:
       build:


Grunt: Also build for Tiva-C

Grunt: Also build for Tiva-C

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,17 +1,13 @@
 fs = require 'fs'
 path = require 'path'
-
-microflo_make = "make -f ./node_modules/microflo/Makefile \
-  MICROFLO_SOURCE_DIR=#{process.cwd()}/node_modules/microflo/microflo \
-  MICROFLO=./node_modules/.bin/microflo
-  BUILD_DIR=#{process.cwd()}/build"

 arduino_build = (name, options) ->
   # Wrapper around the `arduino-builder` CLI tool setting up the neccesary paths
   defaults =
     board: 'arduino:avr:uno'
-    sketch: "build/arduino/#{name}/#{name}.cpp"
+    sketch: "build/arduino/#{name}/#{name}.#{options.ext||'cpp'}"
     arduino: process.env.ARDUINO or process.env.HOME + '/arduino-1.8.1'
+    'arm-none-eabi-gcc': '/home/jon/.arduino15/packages/arduino/tools/arm-none-eabi-gcc/4.8.3-2014q1'

   for k,v of defaults
     options[k] = v if not options[k]?
@@ -24,6 +20,7 @@
     '-hardware', path.join(options.arduino, 'hardware')
     '-tools', path.join(options.arduino, 'tools-builder'),
     '-tools ', path.join(options.arduino, 'hardware', 'tools')
+    "-prefs=runtime.tools.arm-none-eabi-gcc.path=#{options['arm-none-eabi-gcc']}"
     '-fqbn', options.board,
     options.sketch
   ]
@@ -33,7 +30,7 @@
   defaults =
     microflo: './node_modules/.bin/microflo'
     target: 'linux'
-    out: "build/#{options.target||'linux'}/#{name}/#{name}.cpp"
+    out: "build/#{options.target||'linux'}/#{name}/#{name}.#{options.ext||'cpp'}"
     graph: "graphs/#{name}.fbp"
     library: "./components-#{name}.json"
   for k,v of defaults
@@ -116,7 +113,8 @@
     # "$(node build.js build/arduino/PortalLights/PortalLights.cpp)"

     exec:
-      build_tiva: "#{microflo_make} STELLARIS_GRAPH=graphs/TableLights.fbp build-stellaris"
+      tablelights_arduino_gen: microflo_gen 'TableLights', { target: 'arduino' }
+      tablelights_arduino_build: arduino_build 'TableLights', { board: 'energia:tivac:EK-TM4C123GXL' }
       tablelights_linux_gen: microflo_gen 'TableLights'
       tablelights_linux_comp: microflo_compile 'TableLights'
       tablelights_run: './spec/microflo-linux.sh TableLights 4444 & sleep 5'
@@ -144,6 +142,7 @@
     'exec:portallights_linux_gen', 'exec:portallights_linux_comp',
   ]
   @registerTask 'build-microflo-arduino', [
+    'exec:tablelights_arduino_gen', 'exec:tablelights_arduino_build',
     'exec:portallights_arduino_gen', 'exec:portallights_arduino_build',
   ]
   @registerTask 'run-microflo-linux', [


Cleanup jshint options

Cleanup jshint options

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -13,7 +13,6 @@
     # JSHint
     jshint:
       options:
-        ignores: ['src/scripts/libs/**']
         globals:
           require: true

@@ -66,6 +65,7 @@

       source: [
         'src/scripts/**/*.js'
+        '!src/scripts/libs/**'
       ]

     # JS Beautifier


Remove license text. Order tasks.

Remove license text. Order tasks.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -10,21 +10,8 @@
       banner: "/* ===========================================================\n" +
         "# <%= pkg.name %> - v<%= pkg.version %>\n" +
         "# ==============================================================\n" +
-        "# Copyright (C) 2013 <%= pkg.author.name %>\n" +
-        "#\n" +
-        "# This program is free software; you can redistribute it and/or modify\n" +
-        "# it under the terms of the GNU General Public License as published by\n" +
-        "# the Free Software Foundation; either version 2 of the License, or\n" +
-        "# (at your option) any later version.\n" +
-        "#\n" +
-        "# This program is distributed in the hope that it will be useful,\n" +
-        "# but WITHOUT ANY WARRANTY; without even the implied warranty of\n" +
-        "# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n" +
-        "# GNU General Public License for more details.\n" +
-        "#\n" +
-        "# You should have received a copy of the GNU General Public License along\n" +
-        "# with this program; if not, write to the Free Software Foundation, Inc.,\n" +
-        "# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n" +
+        "# Copyright (C) 2014 <%= pkg.author.name %>\n" +
+        "# Licensed under the MIT license.\n" +
         "*/\n"

     coffeelint:
@@ -95,13 +82,13 @@
   # load plugins that provide the tasks defined in the config
   grunt.loadNpmTasks "grunt-coffeelint"
   grunt.loadNpmTasks "grunt-contrib-clean"
+  grunt.loadNpmTasks "grunt-contrib-coffee"
   grunt.loadNpmTasks "grunt-contrib-concat"
-  grunt.loadNpmTasks "grunt-contrib-coffee"
   grunt.loadNpmTasks "grunt-contrib-watch"
   grunt.loadNpmTasks "grunt-shell"
   grunt.loadNpmTasks "grunt-bump"

   # register tasks
   grunt.registerTask "build", ["clean", "coffeelint", "coffee", "concat"]
+  grunt.registerTask "coverage", ["build", "shell:coverage"]
   grunt.registerTask "test", ["build", "shell:jasmine"]
-  grunt.registerTask "coverage", ["build", "shell:coverage"]


switched to uglify2 and enabled source maps

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -99,6 +99,8 @@
 						'scripts/libs/html5shiv-printshiv.js'
 						'scripts/libs/json2.js'
 						'scripts/scripts.min.js'
+						'scripts/scripts.min.js.map'
+						'scripts/scripts.min.js.src'
 						'styles/styles.min.css'
 					]
 					dest: './dist/'
@@ -230,9 +232,10 @@
 						return '' if shouldExcludeModule

 						contents
-					optimize: 'uglify'
+					optimize: 'uglify2'
 					out: './.temp/scripts/scripts.min.js'
 					preserveLicenseComments: false
+					generateSourceMaps: true
 					skipModuleInsertion: true
 					uglify:
 						# Let uglifier replace variables to further reduce file size.


added grep for tests

added grep for tests

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -54,6 +54,7 @@
 			unit:
 				options:
 					reporter: grunt.option('reporter') or 'spec'
+					grep: grunt.option("grep")
 				src: ["test/unit/js/**/*.js"]
 			acceptance:
 				options:


Clean up Gruntfile

Clean up Gruntfile

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,12 +1,12 @@
 module.exports = (grunt) ->

   # Wowser configuration
-  grunt.initConfig {
-    pkg: grunt.file.readJSON('package.json'),
+  grunt.initConfig
+    pkg: grunt.file.readJSON('package.json')

     # Metadata
-    meta: {
-      core: {
+    meta:
+      core:
         banner: '/**\n' +
                 ' * Wowser v<%= pkg.version %>\n' +
                 ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %> <<%= pkg.homepage %>>\n' +
@@ -16,8 +16,7 @@
                 ' * The contents of this file are subject to the MIT License, under which\n' +
                 ' * this library is licensed. See the LICENSE file for the full license.\n' +
                 ' */\n\n'
-      },
-      ui: {
+      ui:
         banner: '/**\n' +
                 ' * Wowser UI v<%= pkg.version %>\n' +
                 ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %> <<%= pkg.homepage %>>\n' +
@@ -27,159 +26,130 @@
                 ' * The contents of this file are subject to the MIT License, under which\n' +
                 ' * this library is licensed. See the LICENSE file for the full license.\n' +
                 ' */\n\n'
-      }
-    },

     # Cleans build folders
-    clean: {
+    clean:
       core: [
-        'build/scripts/**/*.js',
-        '!build/scripts/<%= pkg.name %>/ui.js',
+        'build/scripts/**/*.js'
+        '!build/scripts/<%= pkg.name %>/ui.js'
         '!build/scripts/<%= pkg.name %>/ui/**/*.js'
-      ],
+      ]
       ui: [
-        'build/scripts/<%= pkg.name %>/ui.js',
+        'build/scripts/<%= pkg.name %>/ui.js'
         'build/scripts/<%= pkg.name %>/ui/**/*.js'
       ]
-    },

     # Compiles CoffeeScript source
-    coffee: {
-      options: {
+    coffee:
+      options:
         bare: true
-      }
-      core: {
-        expand: true,
-        cwd: 'src',
+      core:
+        expand: true
+        cwd: 'src'
         src: [
-          'scripts/**/*.coffee',
-          '!scripts/<%= pkg.name %>/ui.coffee',
+          'scripts/**/*.coffee'
+          '!scripts/<%= pkg.name %>/ui.coffee'
           '!scripts/<%= pkg.name %>/ui/**/*.coffee'
-        ],
-        dest: 'build',
+        ]
+        dest: 'build'
         ext: '.js'
-      },
-      ui: {
-        expand: true,
-        cwd: 'src',
+      ui:
+        expand: true
+        cwd: 'src'
         src: [
-          'scripts/<%= pkg.name %>/ui.coffee',
+          'scripts/<%= pkg.name %>/ui.coffee'
           'scripts/<%= pkg.name %>/ui/**/*.coffee'
-        ],
-        dest: 'build',
+        ]
+        dest: 'build'
         ext: '.js'
-      }
-    },

     # Concatenate compiled JavaScript files
     # Note: Order is significant due to namespacing
-    concat: {
-      core: {
-        options: {
+    concat:
+      core:
+        options:
           banner: '<%= meta.core.banner %>'
-        },
         src: [
-          'build/scripts/<%= pkg.name %>.js',
-          'build/scripts/<%= pkg.name %>/utils/**/*.js',
-          'vendor/byte-buffer/dist/byte-buffer.js',
-          'vendor/jsbn/dist/jsbn.js',
-          'vendor/underscore/underscore.js',
-          'vendor/backbone/backbone.js',
-          'build/scripts/<%= pkg.name %>/crypto/hash/**/*.js',
-          'build/scripts/<%= pkg.name %>/crypto/**/*.js',
-          'build/scripts/<%= pkg.name %>/datastructures/**/*.js',
-          'build/scripts/<%= pkg.name %>/net/**/*.js',
-          'build/scripts/<%= pkg.name %>/entities/**/*.js',
-          'build/scripts/<%= pkg.name %>/expansions/expansion.js',
-          'build/scripts/<%= pkg.name %>/expansions/wotlk/wotlk.js',
-          'build/scripts/<%= pkg.name %>/expansions/wotlk/enums/**/*.js',
-          'build/scripts/<%= pkg.name %>/expansions/wotlk/net/**/*.js',
-          'build/scripts/<%= pkg.name %>/expansions/wotlk/**/*.js',
+          'build/scripts/<%= pkg.name %>.js'
+          'build/scripts/<%= pkg.name %>/utils/**/*.js'
+          'vendor/byte-buffer/dist/byte-buffer.js'
+          'vendor/jsbn/dist/jsbn.js'
+          'vendor/underscore/underscore.js'
+          'vendor/backbone/backbone.js'
+          'build/scripts/<%= pkg.name %>/crypto/hash/**/*.js'
+          'build/scripts/<%= pkg.name %>/crypto/**/*.js'
+          'build/scripts/<%= pkg.name %>/datastructures/**/*.js'
+          'build/scripts/<%= pkg.name %>/net/**/*.js'
+          'build/scripts/<%= pkg.name %>/entities/**/*.js'
+          'build/scripts/<%= pkg.name %>/expansions/expansion.js'
+          'build/scripts/<%= pkg.name %>/expansions/wotlk/wotlk.js'
+          'build/scripts/<%= pkg.name %>/expansions/wotlk/enums/**/*.js'
+          'build/scripts/<%= pkg.name %>/expansions/wotlk/net/**/*.js'
+          'build/scripts/<%= pkg.name %>/expansions/wotlk/**/*.js'
           'build/scripts/<%= pkg.name %>/sessions/**/*.js'
-        ],
+        ]
         dest: 'dist/scripts/<%= pkg.name %>.js'
-      },
-      ui: {
-        options: {
+      ui:
+        options:
           banner: '<%= meta.ui.banner %>'
-        },
-        files: {
+        files:
           'dist/scripts/<%= pkg.name %>-ui.js': [
-            'vendor/angular/angular.js',
-            'vendor/threejs/build/three.js',
-            'vendor/threejs/examples/js/controls/OrbitControls.js',
-            'build/scripts/<%= pkg.name %>/ui.js',
+            'vendor/angular/angular.js'
+            'vendor/threejs/build/three.js'
+            'vendor/threejs/examples/js/controls/OrbitControls.js'
+            'build/scripts/<%= pkg.name %>/ui.js'
             'build/scripts/<%= pkg.name %>/ui/**/*.js'
           ]
-        }
-      }
-    },

     # Lints project files using JSHint
-    jshint: {
-      options: {
-        boss: true,
-        eqnull: true,
+    jshint:
+      options:
+        boss: true
+        eqnull: true
         shadow: true
-      },
-      core: {
+      core:
         src: [
-          'build/scripts/**/*.js',
-          '!build/scripts/<%= pkg.name %>/ui.js',
+          'build/scripts/**/*.js'
+          '!build/scripts/<%= pkg.name %>/ui.js'
           '!build/scripts/<%= pkg.name %>/ui/**/*.js'
         ]
-      },
-      ui: {
+      ui:
         src: [
-          'build/scripts/<%= pkg.name %>/ui.js',
+          'build/scripts/<%= pkg.name %>/ui.js'
           'build/scripts/<%= pkg.name %>/ui/**/*.js'
         ]
-      }
-    },

     # Minified distribution
-    uglify: {
-      core: {
-        options: {
+    uglify:
+      core:
+        options:
           banner: '<%= meta.core.banner %>'
-        },
-        files: {
+        files:
           'dist/scripts/<%= pkg.name %>.min.js': ['<%= concat.core.dest %>']
-        }
-      },
-      ui: {
-        options: {
+      ui:
+        options:
           banner: '<%= meta.ui.banner %>'
-        },
-        files: {
+        files:
           'dist/scripts/<%= pkg.name %>-ui.min.js': ['<%= concat.ui.dest %>']
-        }
-      }
-    },

     # Watch for file changes
-    watch: {
-      grunt: {
-        files: ['Gruntfile.coffee'],
+    watch:
+      grunt:
+        files: ['Gruntfile.coffee']
         tasks: ['build']
-      }
-      core: {
+      core:
         files: [
-          'src/scripts/**/*.coffee',
-          '!src/scripts/<%= pkg.name %>/ui.coffee',
+          'src/scripts/**/*.coffee'
+          '!src/scripts/<%= pkg.name %>/ui.coffee'
           '!src/scripts/<%= pkg.name %>/ui/**/*.coffee'
-        ],
+        ]
         tasks: ['build:core']
-      }
-      ui: {
+      ui:
         files: [
-          'src/scripts/<%= pkg.name %>/ui.coffee',
+          'src/scripts/<%= pkg.name %>/ui.coffee'
           'src/scripts/<%= pkg.name %>/ui/**/*.coffee'
-        ],
+        ]
         tasks: ['build:ui']
-      }
-    }
-  }

   grunt.loadNpmTasks 'grunt-contrib-clean'
   grunt.loadNpmTasks 'grunt-contrib-coffee'


check for the existence of the specified spec or test file before running tests

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -51,6 +51,9 @@
   if process.env.APPSDK_PATH
     appsdk_path = path.join process.env.APPSDK_PATH, 'rui'
     served_paths.unshift path.join(appsdk_path, '../..')
+
+  if spec != '*' and grunt.file.expand("test/**/#{spec}+(Spec|Test).+(js|coffee)").length == 0
+    grunt.warn 'The specified spec or jsspec option does not match any test.'

   appFiles = 'src/apps/**/*.js'
   specFiles = 'test/spec/**/*Spec.coffee'


Fixed "grunt test" process.

Fixed "grunt test" process.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -89,8 +89,6 @@
     "updateVersionNumber"
     "notify:version_number"
     "coffee"
-    "updateTestCoverage"
-    "coveralls"
     # "browserify:dist"
     "jshint"
     "tape"
@@ -100,6 +98,8 @@

   grunt.registerTask "test", [
     "build"
+    "updateTestCoverage"
+    "coveralls"
     "connect"
     "saucelabs-custom"
   ]


Add local server.

Add local server.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -44,18 +44,24 @@

     watch:
       test_only:
-        files: ["test/**/*.coffee"]
-        tasks: ["test"]
+        files: ['test/**/*.coffee']
+        tasks: ['test']
         options:
           interrupt: true

       build_and_test:
-        files: ["test/**/*.coffee"]
-        tasks: ["build", "test"]
+        files: ['test/**/*.coffee']
+        tasks: ['build', 'test']
         options:
           interrupt: true

     connect:
+      server:
+        options:
+          port: 9001
+          base: 'examples'
+          keepalive: true
+
       integration_test:
         options:
           hostname: "*"


Ordena ordem no Gruntfile

Ordena ordem no Gruntfile

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -35,22 +35,23 @@
 		concat:
 			dev:
 				files:
+					'build/js/portal-sku-selector-with-template.js': ['build/templates/template-sku-selector-modal.js', 'build/templates/template-sku-selector-product.js', 'build/js/portal-sku-selector.js']
+					'build/js/portal-quantity-selector-with-template.js': ['build/templates/template-quantity-selector.js', 'build/js/portal-quantity-selector.js']
 					'build/js/portal-accessories-selector-with-template.js': ['build/templates/template-accessories-selector.js', 'build/js/portal-accessories-selector.js']
-					'build/js/portal-sku-selector-with-template.js': ['build/templates/template-sku-selector-modal.js', 'build/templates/template-sku-selector-product.js', 'build/js/portal-sku-selector.js']
+					'build/js/portal-price-with-template.js': ['build/templates/template-price.js', 'build/js/portal-price.js']
+					'build/js/portal-notify-me-with-template.js': ['build/templates/template-notify-me.js', 'build/js/portal-notify-me.js']
 					'build/js/portal-minicart-with-template.js': ['build/templates/template-minicart.js', 'build/js/portal-minicart.js']
-					'build/js/portal-quantity-selector-with-template.js': ['build/templates/template-quantity-selector.js', 'build/js/portal-quantity-selector.js']
-					'build/js/portal-notify-me-with-template.js': ['build/templates/template-notify-me.js', 'build/js/portal-notify-me.js']
-					'build/js/portal-price-with-template.js': ['build/templates/template-price.js', 'build/js/portal-price.js']

 		uglify:
 			main:
 				files:
+					'build/js/portal-template-as-modal.min.js': ['build/js/portal-template-as-modal.js']
+					'build/js/portal-sku-selector-with-template.min.js': ['build/js/portal-sku-selector-with-template.js']
+					'build/js/portal-quantity-selector-with-template.min.js': ['build/js/portal-quantity-selector-with-template.js']
 					'build/js/portal-accessories-selector-with-template.min.js': ['build/js/portal-accessories-selector-with-template.js']
-					'build/js/portal-sku-selector-with-template.min.js': ['build/js/portal-sku-selector-with-template.js']
+					'build/js/portal-price-with-template.min.js': ['build/js/portal-price-with-template.js']
+					'build/js/portal-notify-me-with-template.min.js': ['build/js/portal-notify-me-with-template.js']
 					'build/js/portal-minicart-with-template.min.js': ['build/js/portal-minicart-with-template.js']
-					'build/js/portal-template-as-modal.min.js': ['build/js/portal-template-as-modal.js']
-					'build/js/portal-quantity-selector-with-template.min.js': ['build/js/portal-quantity-selector-with-template.js']
-					'build/js/portal-notify-me-with-template.min.js': ['build/js/portal-notify-me-with-template.js']

 		karma:
 			options:
@@ -81,13 +82,13 @@
 		dustjs:
 			compile:
 				files:
-					'build/templates/template-accessories-selector.js': 'src/templates/accessories-selector.dust'
 					'build/templates/template-sku-selector-modal.js': 'src/templates/sku-selector-modal.dust'
 					'build/templates/template-sku-selector-product.js': 'src/templates/sku-selector-product.dust'
+					'build/templates/template-quantity-selector.js': 'src/templates/quantity-selector.dust'
+					'build/templates/template-accessories-selector.js': 'src/templates/accessories-selector.dust'
+					'build/templates/template-price.js': 'src/templates/price.dust'
+					'build/templates/template-notify-me.js': 'src/templates/notify-me.dust'
 					'build/templates/template-minicart.js': 'src/templates/minicart.dust'
-					'build/templates/template-quantity-selector.js': 'src/templates/quantity-selector.dust'
-					'build/templates/template-notify-me.js': 'src/templates/notify-me.dust'
-					'build/templates/template-price.js': 'src/templates/price.dust'

 		connect:
 			main:


package images

package images

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -29,7 +29,7 @@
             jshint:
                 files: ['js/*.js']
                 tasks: ['jshint']
-
+
         connect:

             livereload:
@@ -65,6 +65,7 @@
                 files: [{
                     expand: true
                     src: [
+                        'image/**'
                         'slides/**'
                         'bower_components/**'
                         'js/**'
@@ -77,7 +78,7 @@
                     filter: 'isFile'
                 }]

-
+

     # Load all grunt tasks.
@@ -125,7 +126,7 @@
             'copy'
         ]

-
+

     # Define default task.
     grunt.registerTask 'default', [


Fixing tasks

Fixing tasks

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -24,14 +24,11 @@
                 files:
                     "docs/demo.html": "_docs/_pages/demo.jade" # TODO : change this
         copy:
-            images:
+            assets:
                 expand: yes
-                src: [ "_docs/_styles/img/**" ]
-                dest: "docs/styles/img/"
-            fonts:
-                expand: yes
-                src: [ "_docs/_styles/fonts/**" ]
-                dest: "docs/styles/fonts/"
+                cwd: "_docs/_styles/"
+                src: [ "img/**", "fonts/**" ]
+                dest: "docs/styles/"
         watch:
             jade:
                 files: "_docs/_pages/*.jade"


Fix double js files

Fix double js files

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -114,13 +114,6 @@
 			jquery:
 				src: comp + 'jquery/jquery.js'
 				dest: js + 'jquery.js'
-			# Distr files
-			distzepto:
-				src: '<%= uglify.zepto.dest %>'
-				dest: dist + 'zepto-pack-min.js'
-			distjquery:
-				src: '<%= uglify.jquery.dest %>'
-				dest: dist + 'jquery-pack-min.js'

 		license:
 			parsley:


Remove some duplicated file paths from Gruntfile

In the Gruntfile, for almost every platform, we have some rules to copy
specific node modules due to freedom module requirements.  This changes
those rules around a bit so that we now just build a list of all the
node modules we want to copy in that way and then use it.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -193,6 +193,23 @@
   '!generic_ui/polymer/vulcanized.js' # vulcanized.html uses vulcanized.static.js
   '!**/*spec*'
 ]
+
+extraFilesForCoreBuilds = [
+  'freedomjs-anonymized-metrics',
+  'freedom-social-firebase',
+  'freedom-social-github',
+  'freedom-social-wechat',
+  'freedom-social-quiver',
+  'freedom-pgp-e2e',
+  'freedom-port-control',
+]
+
+getExtraFilesForCoreBuild = (basePath) ->
+  for spec in extraFilesForCoreBuilds
+    expand: true,
+    cwd: path.join('node_modules', spec, 'dist'),
+    src: ['**'],
+    dest: path.join(basePath, spec)

 gruntConfig = {
   pkg: readJSONFile('package.json')
@@ -518,48 +535,11 @@
           'generic_core'
         ]
         pathsFromThirdPartyBuild: backendThirdPartyBuildPaths
-        files: [
-          {
-            expand: true, cwd: 'node_modules/freedomjs-anonymized-metrics/dist/',
-            src: ['**']
-            dest: chromeAppDevPath + '/freedomjs-anonymized-metrics'
-          },
-          {
-            expand: true, cwd: 'node_modules/freedom-social-firebase/dist/',
-            src: ['**']
-            dest: chromeAppDevPath + '/freedom-social-firebase'
-          },
-          {
-            expand: true, cwd: 'node_modules/freedom-social-github/dist/',
-            src: ['**/*.js', '**/*.json']
-            dest: chromeAppDevPath + '/freedom-social-github'
-          },
-          {
-            expand: true, cwd: 'node_modules/freedom-social-wechat/dist/',
-            src: ['**']
-            dest: chromeAppDevPath + '/freedom-social-wechat'
-          },
-          {
-            expand: true, cwd: 'node_modules/freedom-social-quiver/dist/',
-            src: ['**']
-            dest: chromeAppDevPath + '/freedom-social-quiver'
-          },
-          {
-            expand: true, cwd: 'node_modules/freedom-pgp-e2e/dist/',
-            src: ['**']
-            dest: chromeAppDevPath + '/freedom-pgp-e2e'
-          },
-          {
-            expand: true, cwd: 'node_modules/freedom-port-control/dist/',
-            src: ['**']
-            dest: chromeAppDevPath + '/freedom-port-control'
-          },
-          { # uProxy Icons and fonts
-            expand: true, cwd: 'src/'
-            src: ['icons/128_online.png', 'fonts/*']
-            dest: chromeAppDevPath
-          }
-        ]
+        files: getExtraFilesForCoreBuild(chromeAppDevPath).concat({ # uProxy Icons and fonts
+          expand: true, cwd: 'src/'
+          src: ['icons/128_online.png', 'fonts/*']
+          dest: chromeAppDevPath
+        })
         localDestPath: 'chrome/app/'

     # {
@@ -590,48 +570,11 @@
           'fonts'
         ]
         pathsFromThirdPartyBuild: backendThirdPartyBuildPaths
-        files: [
-          {
-            expand: true, cwd: 'node_modules/freedomjs-anonymized-metrics/dist/',
-            src: ['**']
-            dest: firefoxDevPath + 'data/freedomjs-anonymized-metrics'
-          },
-          {
-            expand: true, cwd: 'node_modules/freedom-social-firebase/dist/',
-            src: ['**']
-            dest: firefoxDevPath + '/data/freedom-social-firebase'
-          },
-          {
-            expand: true, cwd: 'node_modules/freedom-social-github/dist/',
-            src: ['**/*.js', '**/*.json']
-            dest: firefoxDevPath + '/data/freedom-social-github'
-          },
-          {
-            expand: true, cwd: 'node_modules/freedom-social-wechat/dist/',
-            src: ['**']
-            dest: firefoxDevPath + '/data/freedom-social-wechat'
-          },
-          {
-            expand: true, cwd: 'node_modules/freedom-social-quiver/dist/',
-            src: ['**']
-            dest: firefoxDevPath + '/data/freedom-social-quiver'
-          },
-          {
-            expand: true, cwd: 'node_modules/freedom-pgp-e2e/dist/',
-            src: ['**']
-            dest: firefoxDevPath + '/data/freedom-pgp-e2e'
-          },
-          {
-            expand: true, cwd: 'node_modules/freedom-port-control/dist/',
-            src: ['**']
-            dest: firefoxDevPath + '/data/freedom-port-control'
-          },
-          { # lib
-            expand: true, cwd: devBuildPath
-            src: ['interfaces/*.js']
-            dest: firefoxDevPath + '/lib'
-          }
-        ]
+        files: getExtraFilesForCoreBuild(path.join(firefoxDevPath, 'data')).concat({ #lib
+          expand: true, cwd: devBuildPath
+          src: ['interfaces/*.js']
+          dest: firefoxDevPath + '/lib'
+        })
         localDestPath: 'firefox/data'
     firefox_additional:
       files: [
@@ -659,48 +602,11 @@
           'fonts'
         ]
         pathsFromThirdPartyBuild: backendThirdPartyBuildPaths
-        files: [
-          {
-            expand: true, cwd: 'node_modules/freedomjs-anonymized-metrics/dist/',
-            src: ['**']
-            dest: ccaDevPath + '/freedomjs-anonymized-metrics'
-          },
-          {
-            expand: true, cwd: 'node_modules/freedom-social-firebase/dist/',
-            src: ['**']
-            dest: ccaDevPath + '/freedom-social-firebase'
-          },
-          {
-            expand: true, cwd: 'node_modules/freedom-social-github/dist/',
-            src: ['**/*.js', '**/*.json']
-            dest: ccaDevPath + '/freedom-social-github'
-          },
-          {
-            expand: true, cwd: 'node_modules/freedom-social-wechat/dist/',
-            src: ['**']
-            dest: ccaDevPath + '/freedom-social-wechat'
-          },
-          {
-            expand: true, cwd: 'node_modules/freedom-social-quiver/dist/',
-            src: ['**']
-            dest: ccaDevPath + '/freedom-social-quiver'
-          },
-          {
-            expand: true, cwd: 'node_modules/freedom-pgp-e2e/dist/',
-            src: ['**']
-            dest: ccaDevPath + '/freedom-pgp-e2e'
-          },
-          {
-            expand: true, cwd: 'node_modules/freedom-port-control/dist/',
-            src: ['**']
-            dest: ccaDevPath + '/freedom-port-control'
-          },
-          { # uProxy Icons and fonts
-            expand: true, cwd: 'src/'
-            src: ['icons/128_online.png', 'fonts/*']
-            dest: ccaDevPath
-          }
-        ]
+        files: getExtraFilesForCoreBuild(ccaDevPath).concat({ # uProxy Icons and fonts
+          expand: true, cwd: 'src/'
+          src: ['icons/128_online.png', 'fonts/*']
+          dest: ccaDevPath
+        })
         localDestPath: 'cca/app/'
     cca_additional:
       files: [


Removed dead code

Removed dead code

--- src/pex/geom/Geometry.coffee
+++ src/pex/geom/Geometry.coffee
@@ -63,19 +63,6 @@
         dirty ||= attrib.dirty
       return dirty

-    #allocate: (numVertices) ->
-    #  for attribName, attrib of @attribs
-    #    console.log(attrib)
-    #    attrib.length = numVertices
-    #
-    #    for i in [0..numVertices-1] by 1
-    #      if not attrib[i]?
-    #        switch attrib.type
-    #          when 'Vec2' then attrib[i] = new Vec2()
-    #          when 'Vec3' then attrib[i] = new Vec3()
-    #          when 'Vec4' then attrib[i] = new Vec4()
-    #          when 'Color' then attrib[i] = new Color()
-
     addEdge: (a, b) ->
       @addEdges() if !@edges
       @edgeHash = [] if !@edgeHash


Fix comment about shifting arguments

Fix comment about shifting arguments

--- src/coffee/cilantro/models/field.coffee
+++ src/coffee/cilantro/models/field.coffee
@@ -52,7 +52,7 @@
             return

         values: (params, handler, cache=true) ->
-            # Shift argument is params is not supplied
+            # Shift arguments if params is not supplied
             if typeof params is 'function'
                 handler = params
                 cache = handler


Removed iPhone & iPad tests from Sauce-Labs because of false negatives.

--- src/grunt-tasks/saucelabs-custom.coffee
+++ src/grunt-tasks/saucelabs-custom.coffee
@@ -1,11 +1,9 @@
 # https://www.npmjs.com/package/grunt-saucelabs

 addBrowser = (os, name, version) ->
-  return {
-    platform   : os
-    browserName: name
-    version    : version
-  }
+  platform   : os
+  browserName: name
+  version    : version

 browsers_list = [
   addBrowser "Windows 8.1", "firefox"
@@ -19,8 +17,8 @@
   addBrowser "Windows XP" , "internet explorer", "8"
   addBrowser "OS X 10.9"  , "firefox"
   addBrowser "OS X 10.9"  , "chrome"
-  addBrowser "OS X 10.9"  , "iphone"
-  addBrowser "OS X 10.9"  , "ipad"
+  # addBrowser "OS X 10.9"  , "iphone"
+  # addBrowser "OS X 10.9"  , "ipad"
   addBrowser "Linux"      , "firefox"
   addBrowser "Linux"      , "chrome"
   addBrowser "Linux"      , "opera"


Remove decaffeinated squirrel-update.coffee

--- src/main-process/squirrel-update.coffee
+++ src/main-process/squirrel-update.coffee
@@ -1,163 +0,0 @@
-fs = require 'fs-plus'
-path = require 'path'
-{app} = require 'electron'
-Spawner = require './spawner'
-WinShell = require './win-shell'
-WinPowerShell = require './win-powershell'
-
-appFolder = path.resolve(process.execPath, '..')
-rootAtomFolder = path.resolve(appFolder, '..')
-binFolder = path.join(rootAtomFolder, 'bin')
-updateDotExe = path.join(rootAtomFolder, 'Update.exe')
-exeName = path.basename(process.execPath)
-
-if process.env.SystemRoot
-  system32Path = path.join(process.env.SystemRoot, 'System32')
-  setxPath = path.join(system32Path, 'setx.exe')
-else
-  setxPath = 'setx.exe'
-
-# Spawn setx.exe and callback when it completes
-spawnSetx = (args, callback) ->
-  Spawner.spawn(setxPath, args, callback)
-
-# Spawn the Update.exe with the given arguments and invoke the callback when
-# the command completes.
-spawnUpdate = (args, callback) ->
-  Spawner.spawn(updateDotExe, args, callback)
-
-# Add atom and apm to the PATH
-#
-# This is done by adding .cmd shims to the root bin folder in the Atom
-# install directory that point to the newly installed versions inside
-# the versioned app directories.
-addCommandsToPath = (callback) ->
-  installCommands = (callback) ->
-    atomCommandPath = path.join(binFolder, 'atom.cmd')
-    relativeAtomPath = path.relative(binFolder, path.join(appFolder, 'resources', 'cli', 'atom.cmd'))
-    atomCommand = "@echo off\r\n\"%~dp0\\#{relativeAtomPath}\" %*"
-
-    atomShCommandPath = path.join(binFolder, 'atom')
-    relativeAtomShPath = path.relative(binFolder, path.join(appFolder, 'resources', 'cli', 'atom.sh'))
-    atomShCommand = "#!/bin/sh\r\n\"$(dirname \"$0\")/#{relativeAtomShPath.replace(/\\/g, '/')}\" \"$@\"\r\necho"
-
-    apmCommandPath = path.join(binFolder, 'apm.cmd')
-    relativeApmPath = path.relative(binFolder, path.join(process.resourcesPath, 'app', 'apm', 'bin', 'apm.cmd'))
-    apmCommand = "@echo off\r\n\"%~dp0\\#{relativeApmPath}\" %*"
-
-    apmShCommandPath = path.join(binFolder, 'apm')
-    relativeApmShPath = path.relative(binFolder, path.join(appFolder, 'resources', 'cli', 'apm.sh'))
-    apmShCommand = "#!/bin/sh\r\n\"$(dirname \"$0\")/#{relativeApmShPath.replace(/\\/g, '/')}\" \"$@\""
-
-    fs.writeFile atomCommandPath, atomCommand, ->
-      fs.writeFile atomShCommandPath, atomShCommand, ->
-        fs.writeFile apmCommandPath, apmCommand, ->
-          fs.writeFile apmShCommandPath, apmShCommand, ->
-            callback()
-
-  addBinToPath = (pathSegments, callback) ->
-    pathSegments.push(binFolder)
-    newPathEnv = pathSegments.join(';')
-    spawnSetx(['Path', newPathEnv], callback)
-
-  installCommands (error) ->
-    return callback(error) if error?
-
-    WinPowerShell.getPath (error, pathEnv) ->
-      return callback(error) if error?
-
-      pathSegments = pathEnv.split(/;+/).filter (pathSegment) -> pathSegment
-      if pathSegments.indexOf(binFolder) is -1
-        addBinToPath(pathSegments, callback)
-      else
-        callback()
-
-# Remove atom and apm from the PATH
-removeCommandsFromPath = (callback) ->
-  WinPowerShell.getPath (error, pathEnv) ->
-    return callback(error) if error?
-
-    pathSegments = pathEnv.split(/;+/).filter (pathSegment) ->
-      pathSegment and pathSegment isnt binFolder
-    newPathEnv = pathSegments.join(';')
-
-    if pathEnv isnt newPathEnv
-      spawnSetx(['Path', newPathEnv], callback)
-    else
-      callback()
-
-# Create a desktop and start menu shortcut by using the command line API
-# provided by Squirrel's Update.exe
-createShortcuts = (locations, callback) ->
-  spawnUpdate(['--createShortcut', exeName, '-l', locations.join(',')], callback)
-
-# Update the desktop and start menu shortcuts by using the command line API
-# provided by Squirrel's Update.exe
-updateShortcuts = (callback) ->
-  if homeDirectory = fs.getHomeDirectory()
-    desktopShortcutPath = path.join(homeDirectory, 'Desktop', "#{app.getName()}.lnk")
-    # Check if the desktop shortcut has been previously deleted and
-    # and keep it deleted if it was
-    fs.exists desktopShortcutPath, (desktopShortcutExists) ->
-      locations = ['StartMenu']
-      locations.push 'Desktop' if desktopShortcutExists
-
-      createShortcuts locations, callback
-  else
-    createShortcuts ['Desktop', 'StartMenu'], callback
-
-# Remove the desktop and start menu shortcuts by using the command line API
-# provided by Squirrel's Update.exe
-removeShortcuts = (callback) ->
-  spawnUpdate(['--removeShortcut', exeName], callback)
-
-exports.spawn = spawnUpdate
-
-# Is the Update.exe installed with Atom?
-exports.existsSync = ->
-  fs.existsSync(updateDotExe)
-
-# Restart Atom using the version pointed to by the atom.cmd shim
-exports.restartAtom = (app) ->
-  if projectPath = global.atomApplication?.lastFocusedWindow?.projectPath
-    args = [projectPath]
-  app.once 'will-quit', -> Spawner.spawn(path.join(binFolder, 'atom.cmd'), args)
-  app.quit()
-
-updateContextMenus = (callback) ->
-  WinShell.fileContextMenu.update ->
-    WinShell.folderContextMenu.update ->
-      WinShell.folderBackgroundContextMenu.update ->
-        callback()
-
-# Handle squirrel events denoted by --squirrel-* command line arguments.
-exports.handleStartupEvent = (app, squirrelCommand) ->
-  switch squirrelCommand
-    when '--squirrel-install'
-      createShortcuts ['Desktop', 'StartMenu'], ->
-        addCommandsToPath ->
-          WinShell.fileHandler.register ->
-            updateContextMenus ->
-              app.quit()
-      true
-    when '--squirrel-updated'
-      updateShortcuts ->
-        addCommandsToPath ->
-          WinShell.fileHandler.update ->
-            updateContextMenus ->
-              app.quit()
-      true
-    when '--squirrel-uninstall'
-      removeShortcuts ->
-        removeCommandsFromPath ->
-          WinShell.fileHandler.deregister ->
-            WinShell.fileContextMenu.deregister ->
-              WinShell.folderContextMenu.deregister ->
-                WinShell.folderBackgroundContextMenu.deregister ->
-                  app.quit()
-      true
-    when '--squirrel-obsolete'
-      app.quit()
-      true
-    else
-      false


Fixed hubots against humanity plugin.

Fixed hubots against humanity plugin.

--- src/scripts/hubotagainsthumanity.coffee
+++ src/scripts/hubotagainsthumanity.coffee
@@ -550,13 +550,9 @@
 ]

 module.exports = (robot) ->
-  robot.respond /card(?: me)?(?: (\d+))?/i
-    count = if msg.match[1]
-                     parseInt(msg.match[1])
-                   else
-                     1
-    for i in [0..count] by 1
-      msg.send msg.random answers
+  robot.respond /card(?: me)?(?: )(\d+)?/i, (msg) ->
+    count = if msg.match[1]? then parseInt(msg.match[1], 10) else 1
+    msg.send msg.random answers for i in [1..count]

   robot.respond /q(?:uestion)? card/i, (msg) ->
     msg.send msg.random questions


Only start application once

Only start application once

--- src/server/manager/tasks/forever.coffee
+++ src/server/manager/tasks/forever.coffee
@@ -9,12 +9,10 @@

 	constructor: (@server) ->

-		restarts = 3
-
 		@path = "#{@server.options.root}/#{@server.options.build}/#{@server.options.server}/#{@server.options.app}"

 		@child = new (forever.Monitor) @path,
-			max:			restarts
+			max:			1
 			watch:			false
 			killTree:		true
 			spinSleepTime:	1000


(test) add a test for creation succeeding with a state field in the csv.

--- src/spec/integration/import.spec.coffee
+++ src/spec/integration/import.spec.coffee
@@ -132,6 +132,36 @@
             .where("productType(id=\"#{@productType.id}\")")
             .staged true
             .build()
+          method: 'GET'
+        }
+        @client.execute request
+      .then (result) =>
+        expect(_.size result.body.results).toBe 1
+        p = result.body.results[0]
+        expect(p.name).toEqual en: @newProductName
+        expect(p.slug).toEqual en: @newProductSlug
+        expect(p.key).toEqual 'productKey'
+        expect(p.state).toBeUndefined
+        expect(p.masterVariant.key).toEqual 'variantKey'
+        done()
+      .catch (err) -> done _.prettify(err)
+
+    it 'should import a simple product, without a state field in the csv', (done) ->
+      csv =
+        """
+        productType,name,variantId,slug,key,variantKey
+        #{@productType.id},#{@newProductName},1,#{@newProductSlug},productKey,variantKey
+        """
+      @importer.import(csv)
+      .then (result) =>
+        expect(_.size result).toBe 1
+        expect(result[0]).toBe '[row 2] New product created.'
+        service = TestHelpers.createService(project_key, 'productProjections')
+        request = {
+          uri: service
+          .where("productType(id=\"#{@productType.id}\")")
+          .staged true
+          .build()
           method: 'GET'
         }
         @client.execute request


Handle other methods of creating a select binding

--- src/view/bindings/select_binding.coffee
+++ src/view/bindings/select_binding.coffee
@@ -14,6 +14,8 @@
   constructor: (definition) ->
     super
     definition.node.removeAttribute('data-bind')
+    definition.node.removeAttribute('data-source')
+    definition.node.removeAttribute('data-target')
     @backingView.on 'childBindingAdded', @childBindingAdded
     @bind()


Reorder tasks tests

Reorder tasks tests

--- test/functionals/tasks/task.spec.coffee
+++ test/functionals/tasks/task.spec.coffee
@@ -46,6 +46,31 @@
             ended = true

       waitsFor progress(-> ended), 'Timed out', 1000
+
+    describe 'and running cake test', ->
+      it 'should trigger the hooks', (done) ->
+
+        run 'cake', ['test'], (status) ->
+          expect(status).toBe(0)
+          expect(inProject 'test.log')
+            .toContain("""hooks added
+                          beforeTask called
+                          afterTask called""")
+          done()
+
+    describe 'and running cake lint', ->
+      it 'should trigger the hooks', ->
+        ended = false
+        runs ->
+          run 'cake', ['lint'], (status) ->
+            expect(status).toBe(0)
+            expect(inProject 'test.log')
+              .toContain("""hooks added
+                            beforeTask called
+                            afterTask called""")
+            ended = true
+
+        waitsFor progress(-> ended), 'cake lint timed out', 5000

     describe 'and running cake foo', ->
       it 'should trigger the hooks', ->
@@ -134,32 +159,4 @@

         waitsFor progress(-> ended), 'cake version timed out', 5000

-    describe 'and running cake test', ->
-      it 'should trigger the hooks', ->
-        ended = false
-        runs ->
-          run 'cake', ['test'], (status) ->
-            expect(status).toBe(0)
-            expect(inProject 'test.log')
-              .toContain("""hooks added
-                            beforeTask called
-                            afterTask called""")
-            ended = true

-        waitsFor progress(-> ended), 'cake test timed out', 5000
-
-    describe 'and running cake lint', ->
-      it 'should trigger the hooks', ->
-        ended = false
-        runs ->
-          run 'cake', ['lint'], (status) ->
-            expect(status).toBe(0)
-            expect(inProject 'test.log')
-              .toContain("""hooks added
-                            beforeTask called
-                            afterTask called""")
-            ended = true
-
-        waitsFor progress(-> ended), 'cake lint timed out', 5000
-
-


Refigured the format of the invitations API call data.

(again).

Now it's a list of {:email x :id x} maps.

--- js/inner/project.coffee
+++ js/inner/project.coffee
@@ -510,9 +510,8 @@
     false

   invite_team_members: (users) =>
-    to_invite = {}
+    to_invite = ({id: user.id(), email: user.email()} for user in users)
     for user in users
-      to_invite[user.id()] = user.email()
       mixpanel.track "Sent invitation",
         project: @project_name()
         login: user.login


compute: assign project to random host if not assigned; also, check with database for projects host before each operation

--- salvus/compute.coffee
+++ salvus/compute.coffee
@@ -231,10 +231,21 @@
                     cb    : cb
         ], (err) => opts.cb?(err))

-    # compute servers health/load info
-    servers: (opts) =>
+    # choose a random host for a project from the available compute_servers
+    random_host: (opts) =>
         opts = defaults opts,
             cb       : required
+        @database.select
+            table     : 'compute_servers'
+            columns   : ['host', 'experimental']
+            objectify : true
+            cb        : (err, s) =>
+                if err
+                    opts.cb(err)
+                else
+                    v = (x.host for x in s when not x.experimental)
+                    opts.cb(undefined, misc.random_choice(v))
+

     # get a socket connection to a particular compute server
     socket: (opts) =>
@@ -286,7 +297,7 @@
                                     if mesg.event == 'project_state_update'
                                         winston.debug("state_update #{misc.to_json(mesg)}")
                                         p = @_project_cache[mesg.project_id]
-                                        if p?
+                                        if p? and p.host == opts.host  # ignore updates from wrong host
                                             p._state      = mesg.state
                                             p._state_time = new Date()
                                             p.emit(p._state, p)
@@ -466,11 +477,57 @@
             cb             : required
         @project_id = opts.project_id
         @compute_server = opts.compute_server
-        @host = 'localhost'  # todo
         @_state = undefined
         @_state_time = undefined
         @dbg('constructor')()
-        opts.cb(undefined, @)
+        @update_host
+            cb : (err) =>
+                if err
+                    opts.cb(err)
+                else
+                    opts.cb(undefined, @)
+
+    update_host: (opts) =>
+        opts = defaults opts,
+            cb : required
+        host = undefined
+        dbg = @dbg("update_host")
+        t = misc.mswalltime()
+        async.series([
+            (cb) =>
+                @compute_server.database.select
+                    table   : 'projects'
+                    columns : ['compute_server']
+                    where   :
+                        project_id : @project_id
+                    cb      : (err, result) =>
+                        if err
+                            cb(err)
+                        else
+                            if result.length == 1 and result[0][0]
+                                host = result[0][0]
+                            cb()
+            (cb) =>
+                if host?
+                    cb()
+                else
+                    @compute_server.random_host
+                        cb : (err, h) =>
+                            if err
+                                cb(err)
+                            else
+                                host = h
+                                @compute_server.database.update
+                                    table : 'projects'
+                                    set   : {compute_server : @host}
+                                    where : {project_id : @project_id}
+                                    cb    : cb
+        ], (err) =>
+            dbg("time=#{misc.mswalltime(t)}ms")
+            if not err
+                @host = host
+            opts.cb(err)
+        )

     dbg: (method) =>
         (m) => winston.debug("ProjectClient(#{@project_id},#{@host}).#{method}: #{m}")
@@ -483,22 +540,26 @@
             cb      : required
         dbg = @dbg("action")
         dbg("action=#{opts.action}; params=#{misc.to_safe_str(opts.params)}")
-        @compute_server.call
-            host : @host
-            mesg :
-                message.compute
-                    project_id : @project_id
-                    action     : opts.action
-                    args       : opts.args
-            timeout : opts.timeout
-            cb      : (err, resp) =>
+        @update_host
+            cb : (err) =>
                 if err
-                    opts.cb(err)
-                else
-                    if resp.error?
-                        opts.cb(resp.error)
-                    else
-                        opts.cb(undefined, resp)
+                    opts.cb(err); return
+                @compute_server.call
+                    host : @host
+                    mesg :
+                        message.compute
+                            project_id : @project_id
+                            action     : opts.action
+                            args       : opts.args
+                    timeout : opts.timeout
+                    cb      : (err, resp) =>
+                        if err
+                            opts.cb(err)
+                        else
+                            if resp.error?
+                                opts.cb(resp.error)
+                            else
+                                opts.cb(undefined, resp)

     ###
        The state of the project, which is one of:
@@ -565,10 +626,12 @@
             nosave : false
             cb     : required
         args = []
+        dbg = @dbg("close")
         if opts.force
             args.push('--force')
         if opts.nosave
             args.push('--nosave')
+        dbg("force=#{opts.force}; nosave=#{opts.nosave}")
         @_action
             action : "close"
             args   : args
@@ -595,7 +658,7 @@
     # create snapshot, save incrementals to cloud storage
     save: (opts) =>
         opts = defaults opts,
-            max_snapshots : 100
+            max_snapshots : 60
             cb     : required
         @_action
             action : "save"


Ooops.  Async is hard.

Ooops.  Async is hard.

--- scripts/minify.coffee
+++ scripts/minify.coffee
@@ -32,7 +32,7 @@
 		if /aether-python\.js/.test fpath
 			console.log "Skipping #{fpath} due to blacklist"
 			fs.copy fpath, dpath, cb2
-			cb2(null)
+			return cb2(null)

 		if fs.existsSync fpath + '.map'
 			smArgs = [


TODO: more todos

TODO: more todos

--- lib/atomic-radio.coffee
+++ lib/atomic-radio.coffee
@@ -27,6 +27,12 @@
     # Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable
     @subscriptions = new CompositeDisposable
     # Register command that toggles this view
+
+    # TODO: 6 presets
+    # TODO: All stations in Menu
+    # TODO: << >> []
+    # TODO: skinnable
+
     @subscriptions.add atom.commands.add 'atom-workspace', 'atomic-radio:toggle': => @toggle()
     @subscriptions.add atom.commands.add 'atom-workspace', 'atomic-radio:play': => @playToggle()
     @subscriptions.add atom.commands.add 'atom-workspace', 'atomic-radio:prev': => @prevStation()


Output a more informative git tag and/or commit

--- server-src/dev.coffee
+++ server-src/dev.coffee
@@ -15,7 +15,10 @@
         console.log "#{key}: #{val}"
     console.log '---------------------'

-    config.git_commit_id = git.long()
+    config.git_commit_id = if git.isTagDirty()
+         "#{git.tag(true)}-#{git.long()}"
+    else
+        git.tag false
     config.server.ALLOWED_URLS = [
         /^\/$/
         /^\/unit\/\d+$/, # with id


Profile view works

Profile view works

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -1,4 +1,5 @@
-# ATN: update jquery
+# ATN: remove jquery migration:
+#   $.browser
 # ATN: change query nomenclature:
 # raw: text from the data explorer
 # clean: cleaned somehow. how?
@@ -3424,7 +3425,8 @@
             @container.state.view = view
             @$(".link_to_#{@view}_view").addClass 'active'
             @$(".link_to_#{@view}_view").parent().addClass 'active'
-            @new_view()
+            if @query_result?.ready
+                @new_view()

         set_scrollbar: =>
             if @view is 'table'
@@ -3547,7 +3549,7 @@
             @view_object = new @views[@view]
                 parent: @
                 query_result: @query_result
-            @$('.tab-content').html @view_object.$el
+            @$('.tab-content').html @view_object.render().$el
             @init_after_dom_rendered()
             @set_scrollbar()
             # ATN @delegateEvents() ?


Add both toJSON and toJsonString

Add both toJSON and toJsonString

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -1,8 +1,4 @@
-# TODO changefeeds
-# Turn pause/unpause links into pause/play icons?
-# Should there be a loading animation while paused?
-
-# Copyright 2010-2012 RethinkDB, all rights reserved.
+# Copyright 2010-2015 RethinkDB, all rights reserved.
 module 'DataExplorerView', ->
     @state =
         current_query: null
@@ -532,7 +528,11 @@
                     # Add `getField(sttr)`
                     tag = 'getField'
                 else if tag is 'toJsonString, toJSON'
+                    # Add the `toJsonString()` alias
                     tag = 'toJsonString'
+                    @set_doc_description command, tag, @suggestions
+                    # Also add `toJSON()`
+                    tag = 'toJSON'
                 @set_doc_description command, tag, @suggestions

             relations = data['types']


Close cursors

Close cursors

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -2416,6 +2416,8 @@
                 @results_view.render_error(@query, err)

         abort_query: =>
+            if @state.cursor?
+                @state.cursor.close()
             @driver_handler.close_connection()
             @id_execution++
             @toggle_executing false
@@ -2441,7 +2443,7 @@
             # Execute the query
             try
                 if @state.cursor?
-                    @state.cursor.close?
+                    @state.cursor.close()
                 # Separate queries
                 @non_rethinkdb_query = '' # Store the statements that don't return a rethinkdb query (like "var a = 1;")
                 @index = 0 # index of the query currently being executed


Refactored handleElement so it matches similar function in JSON Schema.

--- lib/formats/json.coffee
+++ lib/formats/json.coffee
@@ -206,20 +206,27 @@
   cb null, null  # empty representation is null

+# *Element* handler factory.
+createElementHandler = (resolvedType) ->
+  switch resolvedType.name
+    when 'object'
+      handleObjectElement
+    when 'array'
+      handleArrayElement
+    when 'enum'
+      handleEnumElement
+    else
+      handlePrimitiveElement
+
+
 # Generates JSON representation for given *Element* node.
 handleElement = (element, inherited, cb) ->
-  resolveType element, inherited.typeName, (err, resolvedType) ->
-    return cb err if err
-
-    switch resolvedType.name
-      when 'object'
-        handleObjectElement element, resolvedType, inherited, cb
-      when 'array'
-        handleArrayElement element, resolvedType, inherited, cb
-      when 'enum'
-        handleEnumElement element, resolvedType, inherited, cb
-      else
-        handlePrimitiveElement element, resolvedType, inherited, cb
+  async.waterfall [
+    (next) -> resolveType element, inherited.typeName, next
+    (resolvedType, next) ->
+      handle = createElementHandler resolvedType
+      handle element, resolvedType, inherited, next
+  ], cb

 # Transforms given MSON AST into JSON.


More logging

More logging

--- server/methods.coffee
+++ server/methods.coffee
@@ -253,7 +253,7 @@
   closeTrelloBoard: (token, id) ->
     verifyArg(id, 'id')
     verifyArg(token, 'token')
-    logger.debug("Asked to remove Trello board ID #{id}")
+    logger.debug("Asked to remove and close Trello board ID #{id}")

     user = getUser(@)
     board = TrelloBoards.findOne({id: id})
@@ -262,15 +262,16 @@

     appKey = Meteor.settings.public.trelloKey
     try
+      logger.debug("Closing Trello board...")
       result = HTTP.put("https://api.trello.com/1/boards/#{id}/closed?key=#{appKey}&token=#{token}",
         {params: {value: true}}
       )
     catch error
-      logger.warn("Failed to remove Trello board with ID #{id}:")
+      logger.warn("Failed to close Trello board with ID #{id}:")
       logger.warn("Reason for error: '#{error.message}'")
       throw new Meteor.Error("trello-remove", "Failed to remove Trello board with ID #{id}")

-    logger.debug("Removed Trello board successfully (ID: #{id}), removing from database")
+    logger.debug("Closed Trello board successfully (ID: #{id}), removing from database")
     TrelloBoards.remove({id: id})
   getExistingTrelloBoards: (token) ->
     verifyArg(token, 'token')


:memo: Remove EditSession reference

:memo: Remove EditSession reference

--- lib/image-editor.coffee
+++ lib/image-editor.coffee
@@ -3,9 +3,7 @@
 Serializable = require 'serializable'
 {_, fs} = require 'atom'

-# Manages the states between {Editor}s, images, and the project as a whole.
-#
-# Essentially, the graphical version of a {EditSession}.
+# Editor model for an image file
 module.exports =
 class ImageEditor extends Model
   Serializable.includeInto(this)
@@ -52,7 +50,7 @@
   # Returns a {String}.
   getUri: -> @relativePath

-  # Compares two `ImageEditor`s to determine equality.
+  # Compares two {ImageEditor}s to determine equality.
   #
   # Equality is based on the condition that the two URIs are the same.
   #


Fixes broken mouse events on minimap

Fixes broken mouse events on minimap

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -1,5 +1,4 @@
 {$, View, EditorView} = require 'atom'
-{Emitter} = require 'emissary'
 Debug = require 'prolix'
 Delegato = require 'delegato'

@@ -11,7 +10,6 @@
 class MinimapView extends View
   Debug('minimap').includeInto(this)
   Delegato.includeInto(this)
-  Emitter.includeInto(this)

   @delegatesMethods 'getLineHeight', 'getLinesCount', 'getMinimapHeight', 'getMinimapScreenHeight', 'getMinimapHeightInLines', 'getFirstVisibleScreenRow', 'getLastVisibleScreenRow', 'addLineClass', 'removeLineClass', 'removeAllLineClasses', toProperty: 'miniEditorView'

@@ -177,7 +175,7 @@
   updateScroll: =>
     @updateScrollX()
     @updateScrollY()
-    @emit 'minimap:scroll'
+    @trigger 'minimap:scroll'

   updatePositions: ->
     @transform @miniVisibleArea[0], @translate(@indicator.x, @indicator.y)


cleanup #601

cleanup #601

--- lib/misc-command.coffee
+++ lib/misc-command.coffee
@@ -50,23 +50,20 @@
     oldRanges = []

     disposable = @editor.getBuffer().onDidChange ({oldRange, newRange, oldText, newText}) ->
-      switch
-        when not newRange.isEmpty() and oldRange.isEmpty()
-          console.log "Add only"
-          newRanges.push(newRange)
-        when newRange.isEmpty() and not oldRange.isEmpty()
-          console.log "Delete only"
-          oldRanges.push(oldRange)
-        when not newRange.isEmpty() and not oldRange.isEmpty()
-          if newRange.containsRange(oldRange)
-            console.log "replace longer area"
-            newRanges.push(newRange)
-          else
-            console.log 'replace shorter area'
-            newRanges.push(newRange)
-            oldRanges.push(new Range(newRange.end, oldRange.end))
-        when newRange.isEmpty() and oldRange.isEmpty()
-          console.log 'never happens'
+      if newRange.isEmpty() # Remove only
+        return oldRanges.push(oldRange)
+
+      if oldRange.isEmpty() # Add only
+        return newRanges.push(newRange)
+
+      # Both newRange and oldRange are NOT empty, this mean replace.
+      if newRange.containsRange(oldRange)
+        # newRange is longer than oldRange
+        newRanges.push(newRange)
+      else
+        # newRange is shorter than oldRange
+        newRanges.push(newRange)
+        oldRanges.push(new Range(newRange.end, oldRange.end))

     fn()


Fix an admin management bug

Fix an admin management bug

--- app/assets/javascripts/admins.js.coffee
+++ app/assets/javascripts/admins.js.coffee
@@ -25,7 +25,7 @@
              {vlaue: 1, text: 'guest'},
              {vlaue: 2, text: 'operator'},
              {vlaue: 3, text: 'expert'},
-             {vlaue: 4, text: 'admin'},
+             {vlaue: 4, text: 'vip'},
              {vlaue: 5, text: 'root'}]
     ajaxOptions:
       url: url


update

update

--- app/assets/javascripts/common.js.coffee
+++ app/assets/javascripts/common.js.coffee
@@ -48,7 +48,7 @@
     responsiveWindow()

   $("#infbtn input[type='submit']").click ->
-    sleep(200, responsiveWindow)
+    sleep(500, responsiveWindow)

 $ ->
   $("#new_post select").hide()


Sankey: hide loss node

Sankey: hide loss node

--- app/assets/javascripts/d3/sankey.coffee
+++ app/assets/javascripts/d3/sankey.coffee
@@ -339,6 +339,8 @@
         @node_map[n.id] = node
         node

+      @nodes_excluding_loss = _.reject @node_list, (n) -> n.id == 'loss'
+
       @link_list = _.map D3.sankey.charts[k].data.links, (l) =>
         new D3.sankey.Link(_.extend l, view: this)

@@ -383,7 +385,7 @@
         .attr('id', 'loss_gradient').call(
           (g) ->
             g.append('svg:stop').attr('offset', '0%').attr('style', 'stop-color:rgb(250,0,0);stop-opacity:1')
-            g.append('svg:stop').attr('offset', '100%').attr('style', 'stop-color:rgb(250,0,0);stop-opacity:0.1')
+            g.append('svg:stop').attr('offset', '100%').attr('style', 'stop-color:rgb(250,0,0);stop-opacity:0.0')
       )

     draw_links: =>
@@ -409,7 +411,7 @@
     draw_nodes: =>
       colors = d3.scale.category20()
       nodes = @svg.selectAll("g.node")
-        .data(@node_list, (d) -> d.get('id'))
+        .data(@nodes_excluding_loss, (d) -> d.get('id'))
         .enter()
         .append("g")
         .attr("class", "node")
@@ -480,7 +482,7 @@
         .range([0, @height * .90])

       # update the node label
-      @nodes.data(@node_list, (d) -> d.get('id'))
+      @nodes.data(@nodes_excluding_loss, (d) -> d.get('id'))
         .attr("data-tooltip", (d) =>
           h = ""
           for l in d.right_links
@@ -516,11 +518,11 @@
     # returns the height of the tallest column
     max_column_value: =>
       sums = {}
-      for n in @node_list
+      for n in @nodes_excluding_loss
         column = n.get 'column'
         sums[column] = sums[column] || 0
         sums[column] += n.value()
       _.max _.values(sums)

     number_of_columns: =>
-      @__column_number ?= d3.max(@node_list, (n) -> n.get 'column') + 1
+      @__column_number ?= d3.max(@nodes_excluding_loss, (n) -> n.get 'column') + 1


Fix formatting toggle on mobile

Fix formatting toggle on mobile

--- app/assets/javascripts/mobile.js.coffee
+++ app/assets/javascripts/mobile.js.coffee
@@ -12,6 +12,7 @@
 #= require sugar/facebook
 #= require sugar/rich_text
 #= require sugar/timestamps
+#= require sugar/user

 window.toggleNavigation = ->
   $("#navigation").toggleClass "active"


Adding a flag with total counts to the pie chart

--- app/assets/javascripts/result.js.coffee
+++ app/assets/javascripts/result.js.coffee
@@ -9,12 +9,15 @@
         fin = ->
           @sector.stop()
           @sector.scale 1.1, 1.1, @cx, @cy
+          @flag = r.g.popup(@cx, @cy, @sector.value.value or "0")
           if @label
             @label[0].stop()
             @label[0].scale 1.5
             @label[1].attr {"font-weight": 800}
         fout = ->
           @sector.animate {scale: [1, 1, @cx, @cy]}, 500, "bounce"
+          @flag.animate {opacity: 0}, 300, ->
+                                          this.remove()
           if @label
             @label[0].animate {scale: 1}, 500, "bounce"
             @label[1].attr {"font-weight": 400}


Add required checkbox to survey task editor

--- app/classifier/tasks/survey/editor.cjsx
+++ app/classifier/tasks/survey/editor.cjsx
@@ -183,6 +183,15 @@
       <Details summary={Raw task data}>

{JSON.stringify @props.task, null, 2}

+
+

+
+
+        <AutoSave resource={@props.workflow}>
+          <input type="checkbox" name="#{@props.taskPrefix}.required" checked={@props.task.required} onChange={handleInputChange.bind @props.workflow} />{' '}
+          Require at least one identification
+        </AutoSave>
+

   handleFiles: (forEachRow, afterFileHook, e) ->


Fixed: typo

Fixed: typo

--- app/controllers/index_controller.coffee
+++ app/controllers/index_controller.coffee
@@ -15,7 +15,7 @@
 #doctypes
 action 'doctypes', ->

-    jsonRes = []
+    doctypeList = []

     #index several id for test
     #dataSystem.indexId "39bade34f76d6b32234c3974c8004ca9", ["description"]


fix options maybe null if canceled by onDidFailToSetTarget

--- lib/search-input.coffee
+++ lib/search-input.coffee
@@ -62,7 +62,7 @@
       @cancel() unless @finished

   unfocus: ->
-    @editorElement.classList.remove(@options.classList...) if @options.classList?
+    @editorElement.classList.remove(@options.classList...) if @options?.classList?
     @regexSearchStatus.classList.add 'btn-primary'
     @literalModeDeactivator?.dispose()


Fix variable names since changing parameter names.

--- server/router.coffee
+++ server/router.coffee
@@ -13,7 +13,7 @@
     if req.method is 'POST'
       busboy = new Busboy headers: req.headers
       busboy.on 'file', (fieldname, file, filename, encoding, mimetype) ->
-        dir = path.join Meteor.settings.imageDir, id
+        dir = path.join Meteor.settings.imageDir, roomId
         try
           mkdirp.sync dir
           saveTo = path.join dir, filename
@@ -38,7 +38,7 @@
   where: 'server'
 .get ->
   {roomId, file} = @params
-  imagePath = path.join Meteor.settings.imageDir, id, file
+  imagePath = path.join Meteor.settings.imageDir, roomId, file
   if fs.existsSync imagePath
     image = fs.readFileSync imagePath
     @response.writeHead 200,


Simplified.

Simplified.

--- server/search.coffee
+++ server/search.coffee
@@ -13,7 +13,7 @@
           value: "#{ publication.authors[0].lastName } et al."
         ]
       proposals.push [
-        key: "containing"
+        key: ""
         value: query
       ]
       proposals


remove errant console.log

remove errant console.log

--- assets/coffee/components/LogInLink.cjsx
+++ assets/coffee/components/LogInLink.cjsx
@@ -12,7 +12,6 @@
   componentDidMount: ->
     # listener for displaying modal
     @addListener 'LogInLink', 'show-modal', @handleClick
-    console.log 'foo'

   handleClick: ->
     @setState {modalShown: !@state.modalShown}


introduce mixin hook for static setup on host class

--- source/object.coffee
+++ source/object.coffee
@@ -147,6 +147,11 @@
     # Mixin static properties into the host class
     _.extend(this, mixin.Static) if mixin.Static?
     delete mixin.Static
+
+    # Give mixins the chance to do static setup when applied to the host class
+    mixin.onMixinApplied?.call this
+    delete mixin.onMixinApplied
+
     # Helper function to check for object literals only
     isPlainObject = (value) ->
       _.isObject(value) and !_.isArray(value) and !_.isFunction(value)


Port to Process API

Port to Process API

--- spec/Subgraph.coffee
+++ spec/Subgraph.coffee
@@ -24,28 +24,29 @@
       c.inPorts.graph.attach g
       done()

-  class Split extends noflo.Component
-    constructor: ->
-      @inPorts =
-        in: new noflo.Port
-      @outPorts =
-        out: new noflo.ArrayPort
-      @inPorts.in.on 'connect', (data) =>
-        @outPorts.out.connect()
-      @inPorts.in.on 'data', (data) =>
-        @outPorts.out.send data
-      @inPorts.in.on 'disconnect', =>
-        @outPorts.out.disconnect()
-  Split.getComponent = -> new Split
+  Split = ->
+    c = new noflo.Component
+    c.inPorts.add 'in',
+      datatype: 'all'
+    c.outPorts.add 'out',
+      datatype: 'all'
+    c.process (input, output) ->
+      data = input.getData 'in'
+      output.sendDone
+        out: data

   SubgraphMerge = ->
-    inst = new noflo.Component
-    inst.inPorts.add 'in', (event, payload, instance) ->
-      method = event
-      method = 'send' if event is 'data'
-      instance.outPorts[method] 'out', payload
-    inst.outPorts.add 'out'
-    inst
+    c = new noflo.Component
+    c.inPorts.add 'in',
+      datatype: 'all'
+    c.outPorts.add 'out',
+      datatype: 'all'
+    c.forwardBrackets = {}
+    c.process (input, output) ->
+      packet = input.get 'in'
+      return output.done() unless packet.type is 'data'
+      output.sendDone
+        out: packet.data

   describe 'initially', ->
     it 'should be ready', ->


Include error name in spec description / output.

--- spec/compiler.coffee
+++ spec/compiler.coffee
@@ -17,7 +17,7 @@
   itFn = if pending then xit else it

   describe name, ->
-    itFn 'should throw an error', ->
+    itFn "should throw an error named \"#{name}\"", ->
       exercise = -> parser.compile source
       expect(exercise).to.throw Error


more convincing loop test for Bus

more convincing loop test for Bus

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -295,10 +295,10 @@
       ->
         bus = new Bacon.Bus()
         bus.plug(Bacon.later(20, "lol"))
-        bus.plug(bus.filter(=> false))
+        bus.plug(bus.filter((value) => "lol" == value).map(=> "wut"))
         Bacon.later(40).onValue(=> bus.end())
         bus
-      ["lol"])
+      ["lol", "wut"])

 lessThan = (limit) ->


improve tests

improve tests

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -333,6 +333,11 @@
     expectPropertyEvents(
       -> series(1, [1, error(), 2]).toProperty()
       [1, error(), 2])
+
+  it "supports null as value", ->
+    expectPropertyEvents(
+      -> series(1, [null, 1, null]).toProperty(null)
+      [null, null, 1, null])

 describe "Property.map", ->
   it "maps property values", ->
@@ -361,15 +366,6 @@
     values = []
     p.onValue((v) => values.push(v))
     expect(values).toEqual([1])
-  it "preserves null values", ->
-    s = new Bacon.Bus()
-    p = s.toProperty().filter(-> true)
-    p.onValue(=>) # to ensure that property is actualy updated
-    s.push(null)
-    values = []
-    p.onValue((v) => values.push(v))
-    expect(values).toEqual([null])
-

 describe "Property.takeUntil", ->
   it "takes elements from source until an event appears in the other stream", ->
@@ -563,10 +559,9 @@
     bus.scan(0, -> 1).onValue((value) -> outputs.push(value))
     expect(outputs).toEqual([0])
   it "yields null seed value", ->
-    outputs = []
-    bus = new Bacon.Bus()
-    bus.scan(null, -> 1).onValue((value) -> outputs.push(value))
-    expect(outputs).toEqual([null])
+    expectPropertyEvents(
+      -> series(1, [1]).scan(null, ->1)
+      [null, 1])

 describe "combineTemplate", ->
   it "combines streams according to a template object", ->
@@ -841,7 +836,7 @@
 error = (msg) -> new Bacon.Error(msg)
 seqs = []
 soon = (f) -> setTimeout f, t(1)
-timeUnitMillisecs = 5
+timeUnitMillisecs = 10
 series = (interval, values) ->
   Bacon.sequentially(t(interval), values)
 repeat = (interval, values) ->


Fix browser tests

Fix browser tests

--- spec/FindFaces.coffee
+++ spec/FindFaces.coffee
@@ -41,7 +41,8 @@
     beforeEach (done) ->
       # load image, copy to canvas
       if noflo.isBrowser()
-        img = document.createElement('image')
+        fixtures = document.getElementById 'fixtures'
+        img = document.createElement('img')
         canvas = document.createElement('canvas')
         img.onerror = (err) ->
           done(err)
@@ -51,6 +52,7 @@
           canvas.getContext('2d').drawImage(img, 0, 0)
           done()
         img.src = 'being-d4.jpg'
+        fixtures.appendChild img
       else
         fs.readFile __dirname+'/being-d4.jpg', (err, image) ->
           if err
@@ -85,9 +87,9 @@
         ins.beginGroup 'foo'
         ins.send canvas

-      it 'should find 15 faces', ->
+      it 'should find 14+ faces', ->
         chai.expect(results).to.be.an 'array'
-        chai.expect(results.length).to.equal 15
+        chai.expect(results.length).to.be.at.least 14
         chai.expect(grps.length).to.equal 1

       it 'should sort faces by confidence', ->


spec: asar: Test getting stats of root in fs.lstat

--- spec/asar-spec.coffee
+++ spec/asar-spec.coffee
@@ -59,6 +59,14 @@
           done()

     describe 'fs.lstatSync', ->
+      it 'returns information of root', ->
+        p = path.join fixtures, 'asar', 'a.asar'
+        stats = fs.lstatSync p
+        assert.equal stats.isFile(), false
+        assert.equal stats.isDirectory(), true
+        assert.equal stats.isSymbolicLink(), false
+        assert.equal stats.size, 0
+
       it 'returns information of a normal file', ->
         for file in ['file1', 'file2', 'file3', path.join('dir1', 'file1'), path.join('link2', 'file1')]
           p = path.join fixtures, 'asar', 'a.asar', file
@@ -102,6 +110,16 @@
           assert.throws throws, /ENOENT/

     describe 'fs.lstat', ->
+      it 'returns information of root', (done) ->
+        p = path.join fixtures, 'asar', 'a.asar'
+        stats = fs.lstat p, (err, stats) ->
+          assert.equal err, null
+          assert.equal stats.isFile(), false
+          assert.equal stats.isDirectory(), true
+          assert.equal stats.isSymbolicLink(), false
+          assert.equal stats.size, 0
+          done()
+
       it 'returns information of a normal file', (done) ->
         p = path.join fixtures, 'asar', 'a.asar', 'link2', 'file1'
         stats = fs.lstat p, (err, stats) ->


tests: Specify component dir for msgflo-procfile

--- spec/procfile.coffee
+++ spec/procfile.coffee
@@ -19,7 +19,7 @@

       """
       lib = path.join __dirname, 'fixtures', 'library-imgflo.json'
-      options = "--library #{lib} --ignore=imgflo_jobs --ignore=imgflo_api --ignore drop --include 'web: node index.js'"
+      options = "--components participants --library #{lib} --ignore=imgflo_jobs --ignore=imgflo_api --ignore drop --include 'web: node index.js'"
       msgflo_procfile 'imgflo-server.fbp', options, (err, stdout) ->
         chai.expect(err).to.not.exist
         chai.expect(stdout).to.equal expected
@@ -44,7 +44,7 @@
   describe "missing component in library", ->
     it 'should error with helpful message', (done) ->
       @timeout 4000
-      options = "--ignore=imgflo_jobs --ignore=imgflo_api --ignore drop --include 'web: node index.js'"
+      options = "--components participants --ignore=imgflo_jobs --ignore=imgflo_api --ignore drop --include 'web: node index.js'"
       msgflo_procfile 'imgflo-server.fbp', options, (err, stdout, stderr) ->
         chai.expect(err).to.exist
         chai.expect(stderr).to.contain 'No component'


regex match game-instance-id

regex match game-instance-id

--- spec/protocol.coffee
+++ spec/protocol.coffee
@@ -69,13 +69,14 @@
     it "should allow any logged in player to host games", (done) ->
       @anyplayer.emit 'host', { game: "my.game", min: 2, max: 3}
       @lukas.on 'host', (msg) ->
-        expect(msg.game).to.equal "my.game-1234567" # regex test
+        expect(msg.game).to.match /my\.game-.+/
         expect(msg.host).to.equal "Anyname"
         expect(msg.max).to.equal 2
       @anyplayer.on 'host', (msg) ->   # host gets the msg too
-        expect(msg.game).to.equal "my.game-1234567" # regex test
+        expect(msg.game).to.match /my\.game-.+/
         expect(msg.host).to.equal "Anyname"
         expect(msg.max).to.equal 2
+        @gameInstanceID = msg.game
         done()

     it "should deny anyone else to host a new game", (done) ->
@@ -92,11 +93,11 @@
               player:  "Anyname"
               games: [
                 {
-                  game: "my.game-12345" # TODO regex test
+                  game: @anyplayer.gameInstanceID
                   joined:  1
                   min:     2
                   max:     3
-                } # more games ...
+                }
               ]
             },
             { player: "Lukas" },
@@ -107,13 +108,17 @@
           msges_send: 42
         })

-    it "should tell the coplayers when another player joins", (done) ->
-      @anotherplayer.emit 'join', { game: "my.game-12345" } # TODO regex
-      @lukas.emit 'join', { game: "my.game-12345" }
-      @anotherplayer.on 'join', (msg) => this.happens() # expect game instance id
-      @anyplayer.on 'join', (msg) => this.happens() # expect game instance id
+    it "should tell all co-players when another player joins", (done) ->
+      @anotherplayer.emit 'join', { game: @anyplayer.gameInstanceID } # TODO regex
+      @lukas.emit 'join', { game: @anyplayer.gameInstanceID }
+      @anotherplayer.on 'join', (msg) =>
+        this.happens()
+        expect(msg.game).to.equal @anyplayer.gameInstanceID
+      @anyplayer.on 'join', (msg) =>
+        this.happens()
+        expect(msg.game).to.equal @anyplayer.gameInstanceID
       setTimeout ( () =>
-        expect(@happens.calledTwice).to.be.ok #TODO called three times
+        expect(@happens.calledTwice).to.be.ok
         done() ), 42 # ms responsiveness !!!


Forgot to save chances

Forgot to save chances

--- workers/social/lib/social/feeder.coffee
+++ workers/social/lib/social/feeder.coffee
@@ -26,34 +26,29 @@
       activity = JSON.parse message
       regEx = new RegExp "^#{exchangePrefix}"
       owner = ObjectId(exchange.replace regEx, "")
+
+      selector = sourceId: owner, as: "follower"
+      # Get the follower's id
+      cursor = relationshipsCol.find selector, {targetId: true}
+      cursor.each (err, rel) ->
+        if err or not rel
+          #console.log "Failed to find follower", err
+        else
+          selector = {owner: rel.targetId, title: "followed"}
+          # Get the follower's feed
+          feedsCol.findOne selector, _id:true, (err, feed) ->

-      selector = 'profile.nickname': owner
-      accountsCol.findOne selector, _id: true, (err, account) ->
-        if err
-          console.log "Failed to find the publisher account", err
-        else
-          selector = sourceId: owner, as: "follower"
-          # Get the follower's id
-          cursor = relationshipsCol.find selector, {targetId: true}
-          cursor.each (err, rel) ->
-            if err or not rel
-              #console.log "Failed to find follower", err
+            if err or not feed
+              #console.log err
             else
-              selector = {owner: rel.targetId, title: "followed"}
-              # Get the follower's feed
-              feedsCol.findOne selector, _id:true, (err, feed) ->
+              criteria =
+                targetName  : "CActivity"
+                targetId    : activity._id
+                sourceName  : "JFeed"
+                sourceId    : feed._id
+                as          : "container"

-                if err or not feed
-                  #console.log err
-                else
-                  criteria =
-                    targetName  : "CActivity"
-                    targetId    : activity._id
-                    sourceName  : "JFeed"
-                    sourceId    : feed._id
-                    as          : "container"
-
-                  relationshipsCol.update criteria, criteria, {upsert:true}
+              relationshipsCol.update criteria, criteria, {upsert:true}

   assureExchangeMesh: (options) ->


things

things

--- src-cloud/app.coffee
+++ src-cloud/app.coffee
@@ -40,9 +40,9 @@
     user = new Parse.User()
     user.set('username', username)
     user.set('password', password)
-
+
     user.signUp().then((user) ->
-        res.redirect('/')
+        res.render('home')
     , (error) ->
         res.render('signup', flash: error.messsage)
     )