Add config option for stylus images.

Add config option for stylus images.

Closes #145.

--- src/compilers/stylus.coffee
+++ src/compilers/stylus.coffee
@@ -15,13 +15,16 @@

   compile: (files, callback) ->
     mainFilePath = @getAppPath "src/app/styles/main.styl"
+    imagePath = @getAppPath (@options.stylus?.imagePath or "build/web/images")

     fs.readFile mainFilePath, "utf8", (error, data) =>
       return @logError error if error?
       compiler = stylus(data)
         .set("filename", mainFilePath)
         .set("compress", true)
+        .set("paths", [imagePath])
         .set("firebug", @options.stylus?.firebug)
+        .define("image-url", stylus.url({ limit: 0 }))
         .include(@getAppPath "src")

       compiler.use nib if nib


Use selection.isCollapsed to check for empty selections.


既存ファイルのロード処理

既存ファイルのロード処理

--- public/coffee/gdrive.coffee
+++ public/coffee/gdrive.coffee
@@ -27,14 +27,30 @@

     return multipartRequestBody

+  # metadataのget
   get: (fileId, callback) ->
-    request = gapi.client.drive.files.get({
-      'fileId' : fileId
+    gapi.client.load('drive', 'v2', ->
+      request = gapi.client.drive.files.get({
+        'fileId' : fileId
+      })
+      if (!callback)
+        callback = (file) ->
+          console.log(file)
+      request.execute(callback)
+    )
+
+  # fileの中身のget
+  getFileResource: (downloadUrl, accessToken, callback) ->
+    $.ajax({
+      method: 'GET'
+      url: downloadUrl
+      headers: {
+        'Authorization': 'Bearer ' + accessToken
+      }
     })
-    if (!callback)
-      callback = (file) ->
-        console.log(file)
-    request.execute(callback)
+    .always((data) ->
+      callback(data)
+    )

   insert: (title, content, callback) ->
     multipartRequestBody = this.requestBody(title, content)
@@ -77,9 +93,13 @@
 GDriveModel = Backbone.Model.extend({
   initialize: ()->
     this.gdrive = new GDrive()
-    this.access_token = null
-    this.set('title', '')
-    this.set('content', '')
+    this.set({
+      access_token: ''
+      file: null
+      title: ''
+      content: ''
+    })
+
     this.set("client_id", this.gdrive.CLIENT_ID)

   authorize: (callback, caller)->
@@ -109,15 +129,25 @@
           )
     )

-  get: (fileId, callback) ->
+  fetchFile: (fileId, callback, caller) ->
     that = this
     that.gdrive.get(fileId, (file)->
       that.set("file", file)
-
-      callback(file, "get")
-    )
-
-  upload: (title, content, callback) ->
+      that.set('title', file.title)
+
+      that.gdrive.getFileResource(
+        file.downloadUrl,
+        that.get('access_token'),
+        (data) ->
+          that.set('content', data)
+      )
+
+      callback(file, caller)
+    )
+
+  upload: (title, content, callback, caller) ->
+    this.set({title: title, content: content})
+
     if this.get("file")
       this.update(title, content, callback, caller)
     else
@@ -125,6 +155,9 @@

   insert: (title, content, callback, caller) ->
     that = this
+    # memo: gapi.client.loadはGDriveオブジェクトの方に移したい
+    # さらにclient.loadはcallback無しのときにpromiseが返るらしい
+    # https://developers.google.com/api-client-library/javascript/reference/referencedocs
     gapi.client.load('drive', 'v2', ->
       that.gdrive.insert(title, content, (file)->
         console.log("insert file")
@@ -152,7 +185,7 @@
   events:
     'click [name=authorize-button]': 'gapi_authorize'
     'click [name=upload-button]': 'upload'
-    'click [name=picker-button]': 'createPicker'
+    'click [name=picker-button]': 'showPicker'
   initialize: (options)->
     this.alertView = options.alertView # modelなどの特別以外は明示的に受け取る必要がある
     this.model_handler = options.modelHandler
@@ -165,16 +198,9 @@
     this.model.authorize(this.authorizeAlert, this)

   upload: ()->
-<<<<<<< Updated upstream
     title = this.model_handler.getEditorTitle()
     content = this.model_handler.getEditorContent()
-    this.model.upload(title, content, this.uploadAlert, this)
-=======
-    # TODO: ここはeditorのモデルからもらうようにする
-    content = $('#markdown > div > textarea').val()
-    title = $('#markdown > div > [name=title]').val()
-    this.model.upload(title, content, this.uploadAlert)
->>>>>>> Stashed changes
+    this.model.upload(title, content, this.uploadFileAlert, this)

   updateDocumentLink: ()->
     console.log("update_document_link")
@@ -182,6 +208,7 @@
     if file
       documentLink = $('#document-link')
       documentLink.text(file.title)
+      # TODO: file.alternateLinkで同じアドレス取れそう
       documentLink.attr("href", "https://drive.google.com/file/d/" \
        + file.id + "/view")
       documentLink.removeClass("text-muted").addClass("text-primary")
@@ -194,17 +221,18 @@
     else
       caller.alertView.show("warning", "Fail GoogleDrive authorization")

-  uploadAlert: (file, methodName, caller)->
+  uploadFileAlert: (file, methodName, caller)->
     if !file.error
       caller.alertView.show("info", "Success " + methodName + "!")
     else
       caller.alertView.show("danger", "Fail " + methodName + "!")

-  createPicker: ()->
-    console.log("picker create")
-    this.showPicker(this)
-
-  showPicker: (that)->
+  fetchFileAlert: (file, caller)->
+    if !file.error
+      caller.alertView.show("info", "Success " + "fetch" + "!")
+
+  showPicker: ()->
+    that = this
     client_id = that.model.get('client_id')
     access_token = that.model.get("access_token")
     if access_token
@@ -214,19 +242,17 @@
         .addView(view)
         .setAppId(client_id)
         .setOAuthToken(access_token)
+        .setCallback((data) ->
+          if data.action == google.picker.Action.PICKED
+            fileId = data.docs[0].id
+            that.model.fetchFile(fileId, that.fetchFileAlert, that)
+        )
         # .setDeveloperKey(developerKey)
-        .setCallback(that.pickerCallback)
         # .enableFeature(google.picker.Feature.NAV_HIDDEN)
         # .enableFeature(google.picker.Feature.MULTISELECT_ENABLED)
         .build()
       console.log(picker)
       picker.setVisible(true)
-
-  pickerCallback: (data)->
-    if data.action == google.picker.Action.PICKED
-      fileId = data.docs[0].id
-      console.log(data.docs[0])
-      # TODO: idは取れたのでそこから読みだして中身を展開する必要がある
 })

 module.exports = {


DE17705 added a test to make sure the correct icons show on kanban cards

--- kanban/KanbanAppSpec.coffee
+++ kanban/KanbanAppSpec.coffee
@@ -319,6 +319,16 @@
       expect(notificationStub).toHaveBeenCalled()
       args = notificationStub.getCall(0).args[0]
       expect(args.message).toBe 'Invalid query: (Foo = Bar)'
+
+  it 'should have correct icons on cards', ->
+    @createApp().then =>
+      expect(@app.getEl().query('.rally-card-icon').length).toBe 5
+      expect(@app.getEl().query('.card-gear-icon').length).toBe 1
+      expect(@app.getEl().query('.card-plus-icon').length).toBe 1
+      expect(@app.getEl().query('.card-ready-icon').length).toBe 1
+      expect(@app.getEl().query('.card-blocked-icon').length).toBe 1
+      expect(@app.getEl().query('.card-color-icon').length).toBe 1
+

   helpers


Handle error when cannot detect slide information. Show alert warning instead of silently failt

--- coffee/app.coffee
+++ coffee/app.coffee
@@ -1,8 +1,8 @@
 'use strict'

-define ['jquery-private', 'underscore', 'backbone', 'sha1', 'firebase'],  ($_, _, Backbone, sha1, __Firebase__) ->
+define ['jquery-private', 'underscore', 'backbone', 'sha1', 'firebase'],  ($j, _, Backbone, sha1, __Firebase__) ->
+  $=$j
   BACKEND_DATA_HOST = "https://qslider.firebaseio.com/"
-
   Connection = Backbone.Model.extend
     defaults:
       from: 'unknow'
@@ -55,11 +55,11 @@
           .css('padding', '0.5em')

       this.$el.html(this.template({device: @device}))
-      $('body').append this.$el
+      $j('body').append this.$el
       return this

     toggleBoard: () ->
-      $('#qcommander').show()
+      $j('#qcommander').show()

   WelcomeView = Backbone.View.extend
     tagName: 'div'
@@ -232,18 +232,22 @@

       @connection = new Connection code
       @connection.set('bc', bc)
-      @remote = new Remote code.url
-      @connection.set 'author', @remote.getAuthor()
-      @connection.set 'currentSlideUrl', @remote.driver.getCurrentSlideScreenshot()
-      @connection.set 'currentSlideNumber', @remote.driver.getCurrentSlideNumber()
-      @connection.set 'quantity', @remote.driver.quantity
-      @connection.set 'title',    @remote.driver.getTitle()
+      try
+        @remote = new Remote code.url
+        @connection.set 'author', @remote.getAuthor()
+        @connection.set 'currentSlideUrl', @remote.driver.getCurrentSlideScreenshot()
+        @connection.set 'currentSlideNumber', @remote.driver.getCurrentSlideNumber()
+        @connection.set 'quantity', @remote.driver.quantity
+        @connection.set 'title',    @remote.driver.getTitle()
+
+      catch error
+        alert(error)
+        return false

       baseFirebaseUrl = @baseFirebaseUrl = "#{BACKEND_DATA_HOST}#{@connection.get('token')}/"
       @queue.url = "#{baseFirebaseUrl}qc/"
       console? && console.log @queue

-
       # Push slide info
       slideInfo = new Firebase "#{baseFirebaseUrl}info/"
       slideInfo.set @connection.toJSON(), (e) ->
@@ -281,7 +285,7 @@

       this.$el.html(this.template(@connection.attributes))
       console? && console.log this.$el
-      $('body').append this.$el
+      $j('body').append this.$el
       return this

     events:
@@ -307,12 +311,16 @@
       this.setupRemote()
     # Based on url, load corresponding remote driver
     setupRemote: () ->
-      @driver = switch
-        when @url.indexOf('speakerdeck.com/') > 1 then new SpeakerdeskRemote
-        when @url.indexOf('slideshare.net/') > 1  then new SlideshareRemote
-        when @url.indexOf('scribd.com/') > 1  then new ScribdRemote
-        else new RabbitRemote
-
+      try
+        @driver = switch
+          when @url.indexOf('speakerdeck.com/') > 1 then new SpeakerdeskRemote
+          when @url.indexOf('slideshare.net/') > 1  then new SlideshareRemote
+          when @url.indexOf('scribd.com/') > 1  then new ScribdRemote
+          when @url.indexOf('drive.google.com/') > 1  then new GoogleRemote
+          else throw new Error("Not supported yet")
+        return this
+      catch error
+        throw error

     control: () ->
       @driver
@@ -362,68 +370,70 @@
     getCurrentSlideScreenshot: () ->
     getAuthor: () ->
     getTitle: () ->
-      $(document).prop('title')
+      $j(document).prop('title')

   class SpeakerdeskRemote extends RemoteControlDriver
     constructor: () ->
       super
       # The player is put inside a iframe so, let get its contents
-      @container = $('.speakerdeck-iframe ').contents()
+      @container = $j('.speakerdeck-iframe ').contents()
       @quantity = this.getSlideQuantity()
       console? && console.log @container

     getAuthor: () ->
-      $('#talk-details h2 a').html()
-          #when window.location.host.indexOf('slideshare.net')  then   $('#talk-details h2 a').html()
+      $j('#talk-details h2 a').html()
+          #when window.location.host.indexOf('slideshare.net')  then   $j('#talk-details h2 a').html()
     getTitle: () ->
-        $(document).prop('title').replace("// Speaker Deck", '').trim()
+        $j(document).prop('title').replace("// Speaker Deck", '').trim()

     getSlideQuantity: () ->
-      $('.previews > img', @container).length
+      $j('.previews > img', @container).length

     getCurrentSlideNumber: () ->
-      current_url = $('#player-content-wrapper > #slide_image', @container).prop('src')
+      current_url = $j('#player-content-wrapper > #slide_image', @container).prop('src')
       r = /\/slide_([0-9]+)\./gi
       if m = r.exec(current_url)
         return 1 + parseInt(m[1])
       return false

     getCurrentSlideScreenshot: () ->
-      $('#player-content-wrapper > #slide_image', @container).prop('src')
+      $j('#player-content-wrapper > #slide_image', @container).prop('src')

     jump: (num) ->
-      $('.speakerdeck-iframe ')[0].contentWindow.player.goToSlide num
+      $j('.speakerdeck-iframe ')[0].contentWindow.player.goToSlide num
     next: () ->
-      $('.overnav > .next', @container)[0].click()
+      $j('.overnav > .next', @container)[0].click()
     previous: () ->
-      $('.overnav > .prev', @container)[0].click()
+      $j('.overnav > .prev', @container)[0].click()

   class SlideshareRemote extends RemoteControlDriver
     constructor: () ->
       super
-      @container = $('#svPlayerId')
+      @container = $j('#svPlayerId')
       @quantity = this.getSlideQuantity()
       console? && console.log @container

     getAuthor: () ->
-      $('.title .h-author-name').html()
+      $j('.title .h-author-name').html()

     getSlideQuantity: () ->
-      if $('.slide_container').length > 0
-        return $('.slide_container .slide', @container).length
-      if $('.slidesContainer').length > 0
-        return $('.slidesContainer .jsplBgColorBigfoot').length
+      if $j('.slide_container').length > 0
+        return $j('.slide_container .slide', @container).length
+      if $j('.slidesContainer').length > 0
+        return $j('.slidesContainer .jsplBgColorBigfoot').length

     getCurrentSlideNumber: () ->
-      n = parseInt($('.goToSlideLabel > input', @container).val())
+      if $j('.goToSlideLabel > input', this.container).length == 0
+        throw new Error("This page doesn't contain a valid slide.")
+      n = parseInt($j('.goToSlideLabel > input', @container).val())
       if n<=1
         return 1
       return n

     getCurrentSlideScreenshot: () ->
       n = this.getCurrentSlideNumber()
-      if $('.slide_container > .slide').eq(n-1).find('img').length > 0
-        url = $('.slide_container > .slide').eq(n-1).find('img').prop('src')
+      if $j('.slide_container > .slide').eq(n-1).find('img').length > 0
+        url = $j('.slide_container > .slide').eq(n-1).find('img').prop('src')
         if url.indexOf('image')>=1
           return url
       return "http://placehold.it/320&text=#{n}"
@@ -432,12 +442,12 @@
       e = $.Event 'keyup'
       e.which = 13
       e.keyCode = 13
-      $('.navActions .goToSlideLabel input', @container).val(num).trigger e
+      $j('.navActions .goToSlideLabel input', @container).val(num).trigger e

     next: () ->
-      $('.nav > .btnNext', @container)[0].click()
+      $j('.nav > .btnNext', @container)[0].click()
     previous: () ->
-      $('.nav > .btnPrevious', @container)[0].click()
+      $j('.nav > .btnPrevious', @container)[0].click()

   class ScribdRemote extends RemoteControlDriver


Simplify

Simplify

--- coffee/vex.coffee
+++ coffee/vex.coffee
@@ -160,7 +160,7 @@
         ids = vex.getAllVexes().map(-> $(@).data().vex.id).toArray()
         return false unless ids?.length

-        id = ids.sort().reverse()[0]
+        id = ids.sort().shift()
         $lastVex = vex.getVexByID id
         return false if $lastVex.data().vex.escapeButtonCloses isnt true


Set resumableIndexName to show this new feature

--- sample.coffee
+++ sample.coffee
@@ -9,6 +9,7 @@
 # Default collection name is 'fs'
 myData = FileCollection({
    resumable: true,     # Enable the resumable.js compatible chunked file upload interface
+   resumableIndexName: 'test',  # Don't use the default MongoDB index name, which is 94 chars long
    http: [ { method: 'get', path: '/md5/:md5', lookup: (params, query) -> return { md5: params.md5 }}]}
    # Define a GET API that uses the md5 sum id files
 )


fix tests

fix tests

--- components/my_active_bids/test/template.coffee
+++ components/my_active_bids/test/template.coffee
@@ -2,13 +2,13 @@
 fixture = -> [
   {
     "id": "56ba482e8b3b8167d7000000",
-    "highest_bid": {
-      "display_amount_dollars": "$4,000"
-    },
     "sale_artwork": {
       "id": "ed-ruscha-cockroaches-from-insects-portfolio",
       "lot_number": "10",
       "bidder_positions_count": 5,
+      "highest_bid": {
+        "amount_cents": "$4,000"
+      },
       "artwork": {
         "image": {
           "url": "https://d32dm0rphc51dk.cloudfront.net/0-AL7CEZ5IDjCWdNxwjmBg/tall.jpg"
@@ -24,14 +24,17 @@

 describe 'My Active Bids template', ->

+  beforeEach ->
+    @locals =
+      myActiveBids: fixture()
+      accounting: formatMoney: (s) -> s
+
   it 'renders highest bid if the highest_bid on the sale artwork and \
       bidder positon match', ->
-    data = fixture()
-    data[0].is_winning = true
-    template(myActiveBids: data).should.containEql 'Highest Bid'
+    @locals.myActiveBids[0].is_winning = true
+    template(@locals).should.containEql 'Highest Bid'

   it 'renders losing if the highest_bid on the sale artwork and \
       bidder positon do not match', ->
-    data = fixture()
-    data[0].is_winning = false
-    template(myActiveBids: data).should.containEql 'Outbid'
+    @locals.myActiveBids[0].is_winning = false
+    template(@locals).should.containEql 'Outbid'


removed unneeded parenthesis

removed unneeded parenthesis

--- core/app/backbone/models/user.coffee
+++ core/app/backbone/models/user.coffee
@@ -1,5 +1,5 @@
 class window.User extends Backbone.Model
-  initialize: () -> @channels = []
+  initialize: -> @channels = []

   setChannels: (channels) -> @channels = channels


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/tween.coffee
+++ src/tween.coffee
@@ -16,7 +16,7 @@
     for property, data of I.activeTweens
       if I.age >= data.endTime
         I[property] = data.end
-        activeTr
+        delete activeTweens[]
       else
         f = Easing[data.easing](data.start, data.end)
         t = (I.age - data.startTime) / data.duration


Add point methods

Add point methods

--- src/utils.coffee
+++ src/utils.coffee
@@ -257,17 +257,11 @@

 # Returns the biggest value in an Array
 #
-exports.max = (obj) ->
-	for n in obj
-		if !max or n > max then max = n
-	max
+exports.max = (arr) -> Math.max arr...

 # Returns the lowest value in an Array
 #
-exports.min = (obj) ->
-	for n in obj
-		if !min or n < min then min = n
-	min
+exports.min = (arr) -> Math.min arr...

 # Returns the sum of the elements in an Array
 #
@@ -337,3 +331,35 @@

 exports.domCompleteCancel = (f) ->
 	__domComplete = _.without __domComplete, f
+
+
+exports.pointDistance = (pointA, pointB) ->
+	distance =
+		x: Math.abs(pointB.x - pointA.x)
+		y: Math.abs(pointB.y - pointA.y)
+
+exports.pointInvert = (point) ->
+	point =
+		x: 0 - point.x
+		y: 0 - point.y
+
+exports.pointTotal = (point) ->
+	point.x + point.y
+
+exports.frameSize = (frame) ->
+	size =
+		width: frame.width
+		height: frame.height
+
+exports.framePoint = (frame) ->
+	point =
+		x: frame.x
+		y: frame.y
+
+exports.pointAbs = (point) ->
+	point =
+		x: Math.abs point.x
+		y: Math.abs point.y
+
+
+


fix node length calcualtion implementation

--- src/utils.coffee
+++ src/utils.coffee
@@ -103,13 +103,10 @@
   getNodeLength: (node) ->
     return 0 unless node?
     if node.nodeType == node.ELEMENT_NODE
-      if node.classList.contains(Scribe.Constants.SPECIAL_CLASSES.EXTERNAL)
-        return 0
-      externalNodes = node.querySelectorAll(".#{Scribe.Constants.SPECIAL_CLASSES.EXTERNAL}")
-      length = _.reduce(externalNodes, (length, node) ->
-        return length - node.textContent.length
-      , node.textContent.length)
-      return length + (if Scribe.Line.isLineNode(node) then 1 else 0)
+      return 0 unless Scribe.DOM.canEdit(node)
+      return _.reduce(Scribe.DOM.filterUneditable(node.childNodes), (length, child) ->
+        return length + Scribe.Utils.getNodeLength(child)
+      , (if Scribe.Line.isLineNode(node) then 1 else 0))
     else if node.nodeType == node.TEXT_NODE
       return node.textContent.length
     else


Update utils.coffee

Update utils.coffee

--- src/utils.coffee
+++ src/utils.coffee
@@ -96,4 +96,5 @@
   #
   # If it's not available, just return the original URL.
   getSizedImageUrl: (src, size) ->
+    if src === null then 'https://cdn.shopify.com/s/images/admin/no-image-' + size + '.gif'
     if window.Shopify?.Image?.getSizedImageUrl? then Shopify.Image.getSizedImageUrl(src, size) else src


Fixed cdn.

Fixed cdn.

--- src/watch.coffee
+++ src/watch.coffee
@@ -53,7 +53,8 @@
         _.extend settings, value
     else
       settings[key] = value
-  assetHost = settings.assetHost ? ''
+  if settings.assetHost
+    assetHost = settings.assetHost

 setCDN = (s) ->
   assetHost = s


The config path is in options.config and not options.configPath.

--- src/watch.coffee
+++ src/watch.coffee
@@ -226,7 +226,7 @@

 # Load brunch plugins, group them and initialise file watcher.
 #
-# options      - Object. {configPath[, minify, server, port]}.
+# options      - Object. {config[, minify, server, port]}.
 # configParams - Object. Optional. Params will be set as default config params.
 # onCompile    - Function. Will be executed after every successful compilation.
 # callback     - Function.
@@ -236,7 +236,7 @@
   packages = loadPackages '.'

   # Load config, get brunch packages from package.json.
-  config     = helpers.loadConfig options.configPath, configParams
+  config     = helpers.loadConfig options.config, configParams
   joinConfig = config._normalized.join
   plugins    = getPlugins packages, config


Let's just forget this happened, k?

Let's just forget this happened, k?

--- js-library/src/js/util/modal.coffee
+++ js-library/src/js/util/modal.coffee
@@ -48,7 +48,7 @@
     fct

   stopHighlightingFactlink: (id) ->
-    for $el in $("span.factlink[data-factid=#{id}]")
-      $el.contents().unwrap()
+    $("span.factlink[data-factid=#{id}]").each (i, val) ->
+      $(val).contents().unwrap()

   trigger: (e) -> Factlink.trigger(e)


Serve more tokens

Serve more tokens

--- server.coffee
+++ server.coffee
@@ -4,8 +4,8 @@
 oboe = require('oboe')

 TokenCounter = require('./lib/TokenCounter')
-NTokensInHeartbeat = 150 # Number of tokens to send every so often while processing
-NTokensInResponse = 1000 # Number of tokens to send when request completes
+NTokensInHeartbeat = 200 # Number of tokens to send every so often while processing
+NTokensInResponse = 3000 # Number of tokens to send when request completes
 HeartbeatInterval = 1000 # Minimum number of milliseconds between heartbeats

 app = express()


Server code formatting

Server code formatting

--- server.coffee
+++ server.coffee
@@ -3,26 +3,31 @@
 Timer = require 'timer'
 Event = require 'event'

-# ========== Events ==========
+# ==================== Events ====================
 # Game install
 exports.onInstall = ->
 	initializeColors()
 	initializeGame()

 # Game update
-exports.onUpgrade = !->
+exports.onUpgrade = ->
 	# Normally nothing to do here
 	#initializeColors()
 	#initializeGame()

 # Config changes (by admin or plugin adder)
-exports.onConfig = (config) !->
+exports.onConfig = (config) ->
 	if config.restart
 		log 'Restarting game'
 		initializeGame()

-
-#========== Client calls ==========
+# Get background location from player, TODO: notify player when close to beacon
+exports.onGeoloc = (userId, geoloc) ->
+	log 'Geoloc from ' + Plugin.userName(userId) + '('+userId+'): ', JSON.stringify(geoloc)
+
+
+
+#==================== Client calls ====================
 # Add a beacon (during setup phase)
 exports.client_addMarker = (client, location) ->
 	if Db.shared.peek('gameState') isnt 0
@@ -68,9 +73,6 @@
 # Get clients ingame user ID
 # exports.client_getIngameUserId = (client) ->
 # 	client.reply
-
-exports.onGeoloc = (userId, geoloc) ->
-	log 'Geoloc from ' + Plugin.userName(userId) + '('+userId+'): ', JSON.stringify(geoloc)

 exports.client_log = (userId, message) ->
 	log 'Client:'+Plugin.userName(userId)+":"+userId+": "+message
@@ -188,7 +190,7 @@
 						log 'Capture of beacon ', beacon.key(), ' stopped, left the area'

 # Update the takeover percentage of a beacon depening on current action and the passed time
-updateBeaconPercentage = (beacon) !->
+updateBeaconPercentage = (beacon) ->
 	currentPercentage = beacon.peek 'percentage'
 	action = beacon.peek 'action'
 	actionStarted = beacon.peek 'actionStarted'
@@ -204,8 +206,10 @@
 		beacon.set 'percentage', newPercentage

+
+#==================== Functions called by timers ====================
 # Called by the beacon capture timer
-exports.onCapture = (args) !->
+exports.onCapture = (args) ->
 	beacon = Db.shared.ref 'game', 'beacons', args.beacon
 	nextOwner = beacon.peek('nextOwner')
 	log 'Team ', nextOwner, ' has captured beacon ', beacon.key(), ', players: ', args.players
@@ -242,10 +246,8 @@
     # Modify beacon value
 	beacon.modify 'captureValue', (v) -> v - 1 if beaconValue > 1

-
-
 # Called by the beacon neutralize timer
-exports.onNeutralize = (args) !->
+exports.onNeutralize = (args) ->
 	beacon = Db.shared.ref 'game', 'beacons', args.beacon
 	neutralizer = beacon.peek('nextOwner')
 	log 'Team ', neutralizer, ' has neutralized beacon ', beacon.key(), ', players: ', args.players
@@ -271,7 +273,9 @@
 exports.endGame = !->
 	log "The game ended!"

-# ========== Functions ==========
+
+
+# ==================== Functions ====================
 # Get a string of the players that are inRange of a beacon
 getInrangePlayers = (beacon) ->
 	playersStr = undefined
@@ -312,7 +316,7 @@
 		}

 # Game timer
-setTimer = !->
+setTimer = ->
 	if Db.shared.peek('game', 'roundTimeUnit') is 'Months'
 		seconds = Db.shared.peek('game', 'roundTimeNumber')*2592000
 	else if Db.shared.peek('game', 'roundTimeUnit') is 'Days'


Added: processes management when closing the server

--- server.coffee
+++ server.coffee
@@ -16,6 +16,11 @@
         'fileName': fileName
         'channel': channel
         'value': "#{data}"
+
+killCommands = (commands) ->
+    for fileName, command of commands
+        console.log "Killing process for #{fileName}..."
+        command.kill 'SIGTERM'

 startProcess = (socket, fileName) ->

@@ -45,8 +50,15 @@

     socket.on "disconnect", () ->
         console.log "Client has disconnected, closing the processes..."
+        killCommands(commands);

-        for fileName, command of commands
+process.on 'SIGINT', () ->
+  console.log "Server is stopping, closing the processes..."
+  killCommands(commands);
+  app.close()
+  process.exit()

-            console.log "Killing process for #{fileName}..."
-            command.kill 'SIGTERM'
+process.on 'SIGTERM', () ->
+  console.log "Server is stopping, closing the processes..."
+  killCommands(commands);
+  app.close()


/status URL

/status URL

--- server.coffee
+++ server.coffee
@@ -14,6 +14,9 @@
 require('zappajs') params, ->

   @use 'logger'
+
+  @get '/status', ->
+    @send 200, 'OK'

   @get /\/(.*)/, ->
     tree_id = @params[0] or 'main'


Fixed spelling.

Fixed spelling.

--- server.coffee
+++ server.coffee
@@ -39,9 +39,9 @@
     return ->

   # Copied from tracker.js.
-  _maybeSupressMoreLogs: (messagesLength) ->
+  _maybeSuppressMoreLogs: (messagesLength) ->
     if typeof Meteor isnt "undefined"
-      if Meteor._supressed_log_expected()
+      if Meteor._suppressed_log_expected()
         Meteor._suppress_log(messagesLength - 1)

   # Copied from tracker.js.
@@ -56,7 +56,7 @@
           message = error.name + ": " + error.message
           printArgs.push message
       printArgs.push error.stack
-      @_maybeSupressMoreLogs printArgs.length
+      @_maybeSuppressMoreLogs printArgs.length

       for printArg in printArgs
         @_debugFunc() printArg


Rewrite lobby refresh loop

Rewrite lobby refresh loop

--- server.coffee
+++ server.coffee
@@ -49,13 +49,17 @@

 lobby = []

-# lobbyRotate is needed to force refreshing of SDP offers
-lobbyRotate = ->
-	if lobby.length > 0
-		user = lobby.shift()
-		user.writeJSON {type: 'refresh'}
-	setTimeout lobbyRotate, 15000
-lobbyRotate()
+# lobbyRefresh is needed to force refreshing of pending SDP offers
+lobbyRefresh = ->
+	newLobby = []
+	while lobby.length > 0
+		c = lobby.shift()
+		if c.sdpOfferTime? and Date.now() - c.sdpOfferTime > 15000
+			c.writeJSON {type: 'refresh'}
+		else
+			newLobby.push c
+	lobby = newLobby
+setInterval lobbyRefresh, 500

 dtNow = ->
 	return "[#{ moment().format('MM/DD/YYYY hh:mm:ss A') }]"
@@ -106,6 +110,7 @@

 	conn.on 'offer', (data) ->
 		conn.sdpOffer = data.sdp
+		conn.sdpOfferTime = Date.now()
 		lobby.push(conn)
 		#console.log "Received SDP offer from #{ conn.id }"
 		#console.log "Conns in lobby: #{ lobby.length }"


Using Future to wait for requireFlush triggered flushes to finish.

--- server.coffee
+++ server.coffee
@@ -1,4 +1,5 @@
 Fiber = Npm.require 'fibers'
+Future = Npm.require 'fibers/future'

 # Tracker.Computation constructor is private, so we are using this object as a guard.
 # External code cannot access this, and will not be able to directly construct a
@@ -14,7 +15,8 @@

     @pendingComputations = []
     @willFlush = false
-    @inFlush = false
+    @inFlush = null
+    @inRequireFlush = false
     @inCompute = false
     @throwFirstError = false
     @afterFlushCallbacks = []
@@ -61,19 +63,35 @@

     if typeof Meteor isnt "undefined"
       Meteor.defer =>
-        @_runFlush()
+        @_runFlush
+          fromRequireFlush: true
     else
       global.setImmediate =>
-        @_runFlush()
+        @_runFlush
+          fromRequireFlush: true

     @willFlush = true

   _runFlush: (options) ->
+    if @inFlush instanceof Future
+      # If there are two runs from requireFlush in sequence, we simply skip the second one, the first
+      # one is still in progress.
+      return if options?.fromRequireFlush
+
+      # We wait for the previous flush from requireFlush to finish before continuing.
+      @inFlush.wait()
+      assert not @inFlush
+
     throw new Error "Can't call Tracker.flush while flushing" if @inFlush

     throw new Error "Can't flush inside Tracker.autorun" if @inCompute

-    @inFlush = true
+    # If this is a run from requireFlush, provide a future so that calls to flush can wait on it.
+    if options?.fromRequireFlush
+      @inFlush = new Future()
+    else
+      @inFlush = true
+
     @willFlush = true
     @throwFirstError = !!options?.throwFirstError

@@ -101,14 +119,19 @@

       finishedTry = true
     finally
+      # We first have to set @inFlush to null, then we can return.
+
+      inFlush = @inFlush
       unless finishedTry
-        @inFlush = false
+        @inFlush = null
+        inFlush.return() if inFlush instanceof Future
         @_runFlush
           finishSynchronously: options?.finishSynchronously
           throwFirstError: false

       @willFlush = false
-      @inFlush = false
+      @inFlush = null
+      inFlush.return() if inFlush instanceof Future
       if @pendingComputations.length or @afterFlushCallbacks.length
         throw new Error "still have more to do?" if options?.finishSynchronously
         Meteor.setTimeout =>


disabled https stuff

disabled https stuff

--- server.coffee
+++ server.coffee
@@ -31,10 +31,10 @@
 app = express()

 #HTTPS settings
-privateKey = fs.readFileSync('/data/express.key','utf8')
-certificate = fs.readFileSync('/data/express.crt','utf8')
+#privateKey = fs.readFileSync('/data/express.key','utf8')
+#certificate = fs.readFileSync('/data/express.crt','utf8')

-credentials = {key: privateKey, cert: certificate}
+#credentials = {key: privateKey, cert: certificate}

 #shutdown handler
 process.on 'SIGTERM', () ->
@@ -57,15 +57,15 @@
 #setup routes
 app.use router

-httpsServer = https.createServer(credentials,app)
+#httpsServer = https.createServer(credentials,app)
 httpServer = http.createServer(app)

 httpServer.timeout = nconf.get('server:timeout')
-httpsServer.timeout = nconf.get('server:timeout')
+#httpsServer.timeout = nconf.get('server:timeout')

 httpServer.listen nconf.get('server:httpPort'), ->
   Statistics.loadStatistics()
   logger.log 'info', 'HTTP Server listening on port ' + nconf.get 'server:httpPort'

-httpsServer.listen nconf.get('server:httpsPort'), ->
-  logger.log 'info', 'HTTPS Server listening on port ' + nconf.get 'server:httpsPort'
+#httpsServer.listen nconf.get('server:httpsPort'), ->
+#  logger.log 'info', 'HTTPS Server listening on port ' + nconf.get 'server:httpsPort'


Fixed collection tests

Fixed collection tests

--- test/generators/collection/sync.coffee
+++ test/generators/collection/sync.coffee
@@ -14,6 +14,25 @@
   SYNC = options.sync
   BASE_COUNT = 5

+  runTest = (collection_type, done) ->
+    model_type = collection_type::model
+
+    queue = new Queue(1)
+    queue.defer (callback) -> model_type.resetSchema(callback)
+    queue.defer (callback) -> Fabricator.create(model_type, BASE_COUNT, {
+      name: Fabricator.uniqueId('model_')
+      created_at: Fabricator.date
+      updated_at: Fabricator.date
+    }, callback)
+    queue.await (err) ->
+      assert.ok(!err, "No errors: #{err}")
+      collection = new collection_type()
+      collection.fetch (err, fetched_collection) ->
+        assert.ok(!err, "No errors: #{err}")
+        assert.equal(BASE_COUNT, collection.models.length, "collection_type Expected: #{BASE_COUNT}\nActual: #{collection.models.length}")
+        assert.equal(BASE_COUNT, fetched_collection.models.length, "Fetched collection_type Expected: #{BASE_COUNT}\nActual: #{fetched_collection.models.length}")
+        done()
+
   ModelCache.configure({enabled: !!options.cache, max: 100}).hardReset() # configure model cache

   class Model extends Backbone.Model
@@ -21,107 +40,46 @@
     schema: BASE_SCHEMA
     sync: SYNC(Model)

-  describe "Backbone.Collection (cache: #{options.cache}, query_cache: #{options.query_cache})", ->
+  describe "Backbone.collection_type (cache: #{options.cache}, query_cache: #{options.query_cache})", ->

     before (done) -> return done() unless options.before; options.before([Model], done)
     after (done) -> callback(); done()
     beforeEach (done) ->
-
       queue = new Queue(1)

       # reset caches
       queue.defer (callback) -> ModelCache.configure({enabled: !!options.cache, max: 100}).reset(callback) # configure model cache
       queue.defer (callback) -> QueryCache.configure({enabled: !!options.query_cache, verbose: false}).reset(callback) # configure query cache
-
-      queue.defer (callback) -> Model.resetSchema(callback)
-
-      queue.defer (callback) -> Fabricator.create(Model, BASE_COUNT, {
-        name: Fabricator.uniqueId('model_')
-        created_at: Fabricator.date
-        updated_at: Fabricator.date
-      }, callback)
-
       queue.await done

-    # it 'fetch models using pre-configured model', (done) ->
-    #   class Collection extends Backbone.Collection
-    #     url: "#{DATABASE_URL}/models"
-    #     model: Model
-    #     sync: SYNC(Collection)
-
-    #   collection = new Collection()
-    #   collection.fetch (err, fetched_collection) ->
-    #     assert.ok(!err, "No errors: #{err}")
-    #     assert.equal(BASE_COUNT, collection.models.length, "Collection Expected: #{BASE_COUNT}\nActual: #{collection.models.length}")
-    #     assert.equal(BASE_COUNT, fetched_collection.models.length, "Fetched Collection Expected: #{BASE_COUNT}\nActual: #{fetched_collection.models.length}")
-    #     done()
+    it 'fetch models using pre-configured model', (done) ->
+      class Collection extends Backbone.Collection
+        url: "#{DATABASE_URL}/models"
+        model: Model
+        sync: SYNC(Collection)
+      runTest Collection, done

     it 'fetch models using default model', (done) ->
       class Collection extends Backbone.Collection
         url: "#{DATABASE_URL}/models"
         sync: SYNC(Collection)

-      runTest = (err) ->
-        return done(err) if err
+      runTest Collection, done

-        collection = new Collection()
-        collection.fetch (err, fetched_collection) ->
-          assert.ok(!err, "No errors: #{err}")
-          assert.equal(BASE_COUNT, collection.models.length, "Collection Expected: #{BASE_COUNT}\nActual: #{collection.models.length}")
-          assert.equal(BASE_COUNT, fetched_collection.models.length, "Fetched Collection Expected: #{BASE_COUNT}\nActual: #{fetched_collection.models.length}")
-          done()
+    it 'fetch models using upgraded model', (done) ->
+      class SomeModel extends Backbone.Model

-      if options.before
-        options.before([Collection::model], runTest)
-      else
-        runTest()
+      class Collection extends Backbone.Collection
+        url: "#{DATABASE_URL}/models"
+        model: SomeModel
+        sync: SYNC(Collection)

-    # it 'fetch models using upgraded model', (done) ->
-    #   class SomeModel extends Backbone.Model
+      runTest Collection, done

-    #   class Collection extends Backbone.Collection
-    #     url: "#{DATABASE_URL}/models"
-    #     model: SomeModel
-    #     sync: SYNC(Collection)
+    it 'fetch models using upgraded model', (done) ->
+      class Collection extends Backbone.Collection
+        url: "#{DATABASE_URL}/models"
+        model: Model
+        sync: SYNC(Collection)

-    #   runTest = (err) ->
-    #     return done(err) if err
-
-    #     collection = new Collection()
-    #     collection.fetch (err, fetched_collection) ->
-    #       assert.ok(!err, "No errors: #{err}")
-    #       assert.equal(BASE_COUNT, collection.models.length, "Collection Expected: #{BASE_COUNT}\nActual: #{collection.models.length}")
-    #       assert.equal(BASE_COUNT, fetched_collection.models.length, "Fetched Collection Expected: #{BASE_COUNT}\nActual: #{fetched_collection.models.length}")
-    #       done()
-
-    #   if options.before
-    #     options.before([Collection::model], runTest)
-    #   else
-    #     runTest()
-
-    # it 'fetch models using upgraded model', (done) ->
-    #   class Collection extends Backbone.Collection
-    #     url: "#{DATABASE_URL}/models"
-    #     model: Model
-    #     sync: SYNC(Collection)
-
-    #   runTest = (err) ->
-    #     return done(err) if err
-
-    #     Model.all (err, models) ->
-    #       assert.ok(!err, "No errors: #{err}")
-    #       assert.ok(models.length, 'Found models')
-
-    #       collection = new Collection(model.toJSON() for model in models)
-    #       for model in models
-    #         found_model = collection.get(model.id)
-    #         if options.cache
-    #           assert.equal(model, found_model, "Model found in cache")
-    #         else
-    #           assert.notEqual(model, found_model, "Model not found in cache")
-    #       done()
-
-    #   if options.before
-    #     options.before([Collection::model], runTest)
-    #   else
-    #     runTest()
+      runTest Collection, done


fix newest selectize version hidden input issue

--- vendor/assets/javascripts/simple_form_extension/selectize.coffee
+++ vendor/assets/javascripts/simple_form_extension/selectize.coffee
@@ -22,6 +22,13 @@

     if (value = @$el.data('value'))
       @initializeValue(value)
+
+    # Fix for newest version of selectize that keeps the hidden input field
+    # instead of converting it to a text field when we call it on a hidden
+    # input.
+    @$el.next('.selectize-control')
+        .find('.selectize-input input[type="hidden"]')
+        .attr('type', 'text')

   initializeValue: (data) ->
     if @single


Fix: Collection uses status reply model for status reply posts

--- lib/assets/javascripts/collection.js.coffee
+++ lib/assets/javascripts/collection.js.coffee
@@ -7,6 +7,8 @@
   @buildModel: (attrs, options = {}) ->
     if attrs.type == TentStatus.config.POST_TYPES.STATUS
       options.model = TentStatus.Models.StatusPost
+    else if attrs.type == TentStatus.config.POST_TYPES.STATUS_REPLY
+      options.model = TentStatus.Models.StatusReplyPost

     super(attrs, options)


For #2318: comment

For #2318: comment

--- src/resources-packaged/ops/javascript/orbeon/xforms/control/Upload.coffee
+++ src/resources-packaged/ops/javascript/orbeon/xforms/control/Upload.coffee
@@ -62,11 +62,13 @@
     uploadDone: () ->
         ajaxResponseProcessed = () =>
             ORBEON.xforms.Events.ajaxResponseProcessedEvent.unsubscribe ajaxResponseProcessed
-            # However, if we don't (thus the progress indicator is still shown), this means some XForms reset the file name
+            # If progress indicator is still shown, this means some XForms reset the file name
+            # NOTE: This is incorrect, see: https://github.com/orbeon/orbeon-forms/issues/2318
             if YD.hasClass this.container, "xforms-upload-state-progress"
                 # So switch back to the file selector, as we won't get a file name anymore
                 @setState "empty"
         # After the file is uploaded, in general at the next Ajax response, we get the file name
+        # NOTE: Not (always?) the case, see: https://github.com/orbeon/orbeon-forms/issues/2318
         ORBEON.xforms.Events.ajaxResponseProcessedEvent.subscribe ajaxResponseProcessed

     # When users press on the cancel link, we cancel the upload, delegating this to the UploadServer.


createDocument should not have to determine parser every time

The createDocument method was determining the best suited parser method every time a page load was requested. This should only be done once.

--- lib/assets/javascripts/turbolinks.js.coffee
+++ lib/assets/javascripts/turbolinks.js.coffee
@@ -97,6 +97,7 @@
     rememberCurrentUrl()
     rememberCurrentState()
     rememberCurrentAssets()
+    createDocument = generateBrowserCompatibleDocumentParser()
     initialized = true

 recallScrollPosition = (page) ->
@@ -126,7 +127,9 @@
   title = doc.querySelector 'title'
   [ title?.textContent, doc.body ]

-createDocument = (html) ->
+createDocument = null
+
+generateBrowserCompatibleDocumentParser = ->
   createDocumentUsingParser = (html) ->
     (new DOMParser).parseFromString html, 'text/html'

@@ -141,9 +144,9 @@
     testDoc = createDocumentUsingParser '<html><body><p>test'

   if testDoc?.body?.childNodes.length is 1
-    createDocumentUsingParser html
+    createDocumentUsingParser
   else
-    createDocumentUsingWrite html
+    createDocumentUsingWrite

 installClickHandlerLast = (event) ->


don't redraw the whole tree view on every event

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -1,12 +1,10 @@
 # TODO ATN
-# views should not completely redraw every added row
 # rate limit events to avoid freezing the browser when there are too many
-# new events should flash
 # many of the fields of the parent view don't get updated when they change
 # table view shows "no results" when it means "no more results" or "no results yet"
-# bottom of display for changefeeds should show "older events were discarded" if that is the case
 # It says "older results ahve been discarded" but they are just hidden and take up memory
 # disabled horizontal scrollbar shows up sometimes
+# the table view should not completely redraw every added row

 # Copyright 2010-2012 RethinkDB, all rights reserved.
 module 'DataExplorerView', ->
@@ -2958,7 +2956,9 @@
                 return
             if @query_result.is_feed or @query_result.size() < @query_result.position + @parent.container.limit
                 @query_result.once 'add', (query_result, row) =>
-                    @add_row row
+                    @parent.render()
+                    if not @parent.container.state.pause_at?
+                        @add_row row
                     @fetch_batch_rows()
                 @query_result.fetch_next()
             else
@@ -2970,24 +2970,32 @@
             @parent.render()
             @fetch_batch_rows()

-        add_row: =>
-            @parent.render()
-            if not @parent.container.state.pause_at?
-                @render()
+        add_row: (row) =>
+            @render()

     class TreeView extends ResultView
         className: 'results tree_view_container'
         template: Handlebars.templates['dataexplorer_result_tree-template']

         render: =>
-            @$el.html @template
-                tree: @json_to_tree @current_batch()
-            # ATN TODO
-            if @query_result.is_feed
-                first_event = @$('.jt_array:first li:first')
-                first_event.css 'background-color': '#eeeeff'
-                first_event.animate 'background-color': '#fbfbfb'
+            switch @query_result.type
+                when 'value'
+                    @$el.html @template tree: @json_to_tree @query_result.value
+                when 'cursor'
+                    @$el.html @template tree: []
+                    for row in @current_batch()
+                        @add_row row, true
             return @
+
+        add_row: (row, noflash) =>
+            tree_container = @$('.json_tree_container')
+            node = $(@json_to_tree(row)).prependTo(tree_container)
+            if not noflash
+                node.css 'background-color': '#eeeeff'
+                node.animate 'background-color': '#fbfbfb'
+            children = tree_container.children()
+            if children.length > @parent.container.limit
+                children.last().remove()

     class TableView extends ResultView
         className: 'results table_view_container'


No longer require parent to be set to allow a custom renderFn

--- src/leap.rigged-hand.coffee
+++ src/leap.rigged-hand.coffee
@@ -202,8 +202,9 @@
   unless scope.parent
     scope.initScene(document.body)
     scope.parent = scope.scene
-    scope.renderFn = ->
-      scope.renderer.render(scope.scene, scope.camera)
+
+  scope.renderFn ||= ->
+    scope.renderer.render(scope.scene, scope.camera)

   projector = new THREE.Projector()


updated utils usage in notification.coffee

--- src/lib/notification.coffee
+++ src/lib/notification.coffee
@@ -25,14 +25,14 @@
     # the outcome of the copy request or the existence of an override message.
     div = document.getElementById 'tip'
     div.className = result;
-    div.innerHTML = ext.message or utils.i18n result
+    div.innerHTML = ext.message or i18n.get result
     # Reset `ext` to avoid affecting copy other copy requests. If the user has
     # disabled the notifications option this should still be called for safety.
     ext.reset()
     # Set a timer to close the notification after a specified period of time,
     # if the user enabled the corresponding option; otherwise it should stay
     # open until it is closed manually by the user.
-    duration = utils.get 'notificationDuration'
+    duration = store.get 'notificationDuration'
     if duration > 0
       window.setTimeout ->
         window.close()


Use new octal syntax for CoffeeScript 1.3

Fixes #11.

--- server.coffee
+++ server.coffee
@@ -181,7 +181,7 @@
 console.log "SSL-Proxy running on #{port} with pid:#{process.pid}."
 console.log "Using the secret key #{shared_key}"

-Fs.open "tmp/camo.pid", "w", 0600, (err, fd) ->
+Fs.open "tmp/camo.pid", "w", 0o600, (err, fd) ->
   Fs.writeSync fd, process.pid

 server.listen port


Add an example of setLivePathConsumer()

Add an example of setLivePathConsumer()

--- server.coffee
+++ server.coffee
@@ -1,3 +1,5 @@
+url = require 'url'
+
 StreamServer = require './stream_server'
 Bits = require './bits'
 logger = require './logger'
@@ -6,6 +8,15 @@
 logger.setLevel logger.LEVEL_INFO

 streamServer = new StreamServer
+streamServer.setLivePathConsumer (uri, callback) ->
+  pathname = url.parse(uri).pathname[1..]
+
+  isAuthorized = true
+
+  if isAuthorized
+    callback null # Accept access
+  else
+    callback new Error 'Unauthorized access' # Deny access

 process.on 'SIGINT', =>
   console.log 'Got SIGINT'


Use 3000 if PORT not defined

Use 3000 if PORT not defined

--- server.coffee
+++ server.coffee
@@ -23,8 +23,9 @@
 api = require './api'
 api app
 # listen
-app.listen process.env.PORT
-logger.info "HTTP REST API listening on port #{process.env.PORT}".green
+port = process.env.PORT || 3000
+app.listen port
+logger.info "HTTP REST API listening on port #{port}".green

 # SHOW INDEX
 # -------------------


corr_w_scatter: Fix initial comment in coffee script

--- corr_w_scatter/corr_w_scatter.coffee
+++ corr_w_scatter/corr_w_scatter.coffee
@@ -1,11 +1,7 @@
-# manyboxplots2.coffee
+# corr_w_scatter.coffee
 #
-# Top panel is like ~500 box plots:
-#   lines are drawn at the 0.1 1, 10, 25, 50, 75, 90, 99, 99.9 percentiles
-#   for each of ~500 distributions
-# Hover over a column in the top panel and the corresponding distribution
-#   is show below; click for it to persist; click again to make it go away.
-#
+# Left panel is a heat map of a correlation matrix; hover over pixels
+# to see the values; click to see the corresponding scatterplot on the right

 d3.json "data.json", (data) ->


authworker: add stub case for authenticating chat

--- workers/auth/lib/auth/authworker.coffee
+++ workers/auth/lib/auth/authworker.coffee
@@ -161,11 +161,11 @@
         when 'bongo', 'kite'
           joinClientHelper.call this, messageData, routingKey, socketId
         when 'group'
-          console.log {routingKey}
           unless ///^group.#{messageData.group}///.test routingKey
             console.log 'rejecting', routingKey
             return @rejectClient routingKey
           joinClientGroupHelper.call this, messageData, routingKey, socketId
+        when 'chat' then # TODO: handle authentication for chat
         when 'secret'
           @addClient socketId, routingKey, routingKey, no
         else


Freitext von Freitextfeldern beim Bearbeiten anzeigen

closes #68

--- app/assets/javascripts/controllers/certificates/templates.js.coffee
+++ app/assets/javascripts/controllers/certificates/templates.js.coffee
@@ -57,6 +57,7 @@
   constructor: (@formElement, @parent) ->
     @description = config[@formElement.get('key')].description
     @example = config[@formElement.get('key')].example
+    @example = @formElement.get('text') if @formElement.get('key') == 'text'
     @parentOffset = @parent.offset()
     buttonLine = $('<div/>').addClass('button-line')
     @textLine = $('<div/>').addClass('text-line')


reuse method in superclass

reuse method in superclass

--- core/app/backbone/collections/followers.coffee
+++ core/app/backbone/collections/followers.coffee
@@ -56,10 +56,8 @@
     @trigger 'change'

   parse: (response) ->
-    @totalRecords = response.total
-    @totalPages = Math.floor(response.total / @perPage)
     @_followed_by_me = response.followed_by_me
-    response
+    super(response)

   followed_by_me: ->
     @_followed_by_me


removed alerts from the success callback on allowedOrientation API calls.. this causes issues since iOS has to display the alert before it has changed the screen orientation and that break thinks.

--- testApp/app/controllers/webview.coffee
+++ testApp/app/controllers/webview.coffee
@@ -197,35 +197,35 @@
     steroids.view.setAllowedRotations {
       allowedRotations: [0]
     }, {
-      onSuccess: -> alert "disabled rotating"
+      onSuccess: -> console.log "disabled rotating"
     }

   @testEnableRotate90: ->
     steroids.view.setAllowedRotations {
       allowedRotations: [90]
     }, {
-      onSuccess: -> alert "allowed rotate to 90"
+      onSuccess: -> console.log "allowed rotate to 90"
     }

   @testEnableRotateAll: ->
     steroids.view.setAllowedRotations {
       allowedRotations: [0, 90, 180, -90]
     }, {
-      onSuccess: -> alert "rotating to all directions"
+      onSuccess: -> console.log "rotating to all directions"
     }

   @testEnableRotateHorizontal: ->
     steroids.view.setAllowedRotations {
       allowedRotations: [-90, 90]
     }, {
-      onSuccess: -> alert "rotates to horizontal directions"
+      onSuccess: -> console.log "rotates to horizontal directions"
     }

   @testEnableRotateVertical: ->
     steroids.view.setAllowedRotations {
       allowedRotations: [0, 180]
     }, {
-      onSuccess: -> alert "rotates to vertical directions"
+      onSuccess: -> console.log "rotates to vertical directions"
     }

   @testOpenWithCurlUp: ->


Fix a test

Fix a test

--- modules/titlebar/main/index.coffee
+++ modules/titlebar/main/index.coffee
@@ -71,7 +71,9 @@
         @_.active = if isString(id) or isElement(id) or isSelection(id)
           select(id).classed('hx-selected', true)
         else
-          select(selection.node(id)).classed('hx-selected', true)
+          node = selection.node(id)
+          if node?
+            select(node).classed('hx-selected', true)
         this
     else
       @_.active


adding colleague back into email template variables when author name is missing

--- noddy/service/oabutton/mail.coffee
+++ noddy/service/oabutton/mail.coffee
@@ -68,6 +68,7 @@
     vars.useremail = vars.user.email
   vars.profession ?= 'person'
   vars.fullname ?= 'a user'
+  vars.name ?= 'colleague'
   return vars

 API.service.oab.substitute = (content,vars,markdown) ->


added lt,gt,lte,gte operators

added lt,gt,lte,gte operators

--- src/liquid/condition.coffee
+++ src/liquid/condition.coffee
@@ -10,6 +10,10 @@
     '==': (cond, left, right) ->  cond.equalVariables(left, right)
     '!=': (cond, left, right) -> !cond.equalVariables(left, right)
     '<>': (cond, left, right) -> !cond.equalVariables(left, right)
+    '<':  (cond, left, right) -> left < right
+    '>':  (cond, left, right) -> left > right
+    '<=': (cond, left, right) -> left <= right
+    '>=': (cond, left, right) -> left >= right
     'contains': (cond, left, right) ->
       if left and right
         left.indexOf(right) >= 0


Remove old bootstrap code

Remove old bootstrap code

--- bootstrap/index.coffee
+++ bootstrap/index.coffee
@@ -8,30 +8,6 @@
 ###

 module.exports = []
-
-# module.exports.push name: 'Bootstrap # WAIT', callback: (ctx, next) ->
-#   toto = 'yo'
-#   setInterval ->
-#     lulu = 'yu'
-#   , 10000
-
-# module.exports.push name: 'Bootstrap # Cache', callback: (ctx, next) ->
-#   ctx.config.bootstrap.cache ?= {}
-#   location = ctx.config.bootstrap.cache.location or './tmp'
-#   mecano.mkdir
-#     destination: location
-#   , (err, created) ->
-#     if ctx.shared.cache
-#       ctx.cache = ctx.shared.cache
-#       return next null, ctx.PASS
-#     db = leveldb location
-#     ctx.cache = ctx.shared.cache =
-#       db: db
-#       put: (key, value, callback) ->
-#         db.put key, value, callback
-#       get: (key, callback) ->
-#         db.get key, callback
-#     next null, ctx.PASS

 module.exports.push 'phyla/bootstrap/cache_file'


Simplify eBook initiated requests

Simplify eBook initiated requests

--- app-ng.coffee
+++ app-ng.coffee
@@ -130,24 +130,24 @@
                 ).on 'close', ->
                     console.log("closing..")

+getSocketForReq = (req) ->
+    if sockets[req.query.key]?
+        return sockets[req.query.key]
+    else if sockets[decodeURIComponent(req.query.key)]?
+        return sockets[decodeURIComponent(req.query.key)]
+    else
+        return null
+
 app.get '/playsong', (req, res) ->
-    console.log(req.query.key)
-    if sockets[req.query.key]?
-        sockets[req.query.key].emit('playsong', title: req.query.title, artist: req.query.artist)
-    else if sockets[decodeURIComponent(req.query.key)]?
-        sockets[decodeURIComponent(req.query.key)].emit('playsong', title: req.query.title, artist: req.query.artist)
-    else
-        console.log("No matching socket found for " + req.query.key)
+    socket = getSocketForReq(req)
+    if socket?
+        socket.emit('playsong', title: req.query.title, artist: req.query.artist)
     res.render 'playsong', userkey: decodeURIComponent(decodeURIComponent(req.query.key))

 app.get '/stop', (req, res) ->
-    console.log(req.query.key)
-    if sockets[req.query.key]?
-        sockets[req.query.key].emit('stop')
-    else if sockets[decodeURIComponent(req.query.key)]?
-        sockets[decodeURIComponent(req.query.key)].emit('stop')
-    else
-        console.log("No matching socket found for " + req.query.key)
+    socket = getSocketForReq(req)
+    if socket?
+        socket.emit('stop')
     res.render 'playsong'

 sockets = {}


Add text in chosen select

Add text in chosen select

--- app/assets/javascripts/chosen.js.coffee
+++ app/assets/javascripts/chosen.js.coffee
@@ -3,4 +3,4 @@
   $('.chosen-select').chosen
     no_results_text: 'No results matched'
     width: '400px'
-    placeholder_text_multiple: 'Select languages...'
+    placeholder_text_multiple: 'Select languages you can read and speak'


Disabled the tkh_editor.

Disabled the tkh_editor.

--- app/assets/javascripts/custom.js.coffee
+++ app/assets/javascripts/custom.js.coffee
@@ -2,6 +2,6 @@
   $("a[rel=popover]").popover()
   $(".tooltip").tooltip()
   $("a[rel=tooltip]").tooltip()
-  $('#editable').wysihtml5()
+  # $('#editable').wysihtml5()
   $('.typeahead').typeahead()
   $('.whatever-man').tooltip()


Fix table classname

Fix table classname

--- app/assets/javascripts/istart.js.coffee
+++ app/assets/javascripts/istart.js.coffee
@@ -24,7 +24,7 @@

   # DataTables: http://datatables.net/
   try
-    $('.datatables').dataTable
+    $('.datatable').dataTable
       "sDom": "<'row-fluid'<'span6'l><'span6'f>r>t<'row-fluid'<'span6'i><'span6'p>>"
       "sPaginationType": "bootstrap"
       "oLanguage":


try non og way

try non og way

--- app/assets/javascripts/social.js.coffee
+++ app/assets/javascripts/social.js.coffee
@@ -38,9 +38,8 @@
   $(".fb-share").click ->
     elem = $(this)
     FB.ui
-      method: "share_open_graph"
-      action_type: "og.likes"
-      action_properties: JSON.stringify(object: elem.prop("href"))
+      method: "share"
+      href: elem.prop('href'),
     , (response) ->
     # postToFeed elem.data("title"), elem.data("desc"), elem.prop("href"), elem.data("image")
     # false


try contructing obj for fb

try contructing obj for fb

--- app/assets/javascripts/social.js.coffee
+++ app/assets/javascripts/social.js.coffee
@@ -36,12 +36,21 @@

 $ ->
   $(".fb-share").click ->
-    elem = $(this)
+    $elem = $(this)
     console.log 'WTF'
     FB.ui
-      method: "share"
-      href: elem.prop('href'),
+      method: "share_open_graph"
+      action_type: "og.likes"
+      action_properties: JSON.stringify(
+        object: {
+          link: $elem.prop('href'),
+          picture: $elem.data('image'),
+          name: $elem.data('title'),
+          description: $elem.data('desc')
+        }
+      )
     , (response) ->
+
     # postToFeed elem.data("title"), elem.data("desc"), elem.prop("href"), elem.data("image")
     # false


CredentialListController: fix linter issue

--- client/app/lib/views/credentiallist/accountcredentiallistcontroller.coffee
+++ client/app/lib/views/credentiallist/accountcredentiallistcontroller.coffee
@@ -42,7 +42,7 @@

     listView.on 'ItemDeleted', @bound 'showNoItemWidget'

-    listView.on 'ItemAction', ({ action, item, options }) =>
+    listView.on 'ItemAction', ({ action, item, options }) ->

       credential    = item.getData()
       { provider }  = credential


minor

minor

--- client/landing/app/MainApp/filetree/bottomlist/finderbottomlistitem.coffee
+++ client/landing/app/MainApp/filetree/bottomlist/finderbottomlistitem.coffee
@@ -1,20 +1,22 @@
 class FinderBottomControlsListItem extends KDListItemView
-  constructor:(options,data)->
-    options = $.extend
-      tagName      : "li"
-    ,options
-    super options,data
+
+  constructor:(options = {},data)->
+
+    options.tagName or= "li"
+
+    super options, data

   click:(event)->
+
     if @getData().path?
-      appManager.openApplication @getData().path if @getData().path?
+      appManager.openApplication @getData().path if @getData().path?
     else if @getData().action is "showShortcuts"
       @showShortcuts()
     else
       new KDNotificationView
         title : "Coming Soon!"
         duration : 1000
-
+
   viewAppended:->
     super

@@ -76,4 +78,3 @@

     @_keyHelperModal.addSubView filetreeKeyHelperController.getView()
     @_keyHelperModal.addSubView editorKeyHelperController.getView()
-


change copy

change copy

--- project/coffee/view/components/HomeTootip.coffee
+++ project/coffee/view/components/HomeTootip.coffee
@@ -19,6 +19,7 @@
     constructor : (@list) ->
         # @templateVars =
         #     list : @list
+

         @nameMap =
             'environment':


Small code style fixes for FileUploadView

--- app/coffeescripts/FileUploadView.coffee
+++ app/coffeescripts/FileUploadView.coffee
@@ -1,9 +1,9 @@
-# This class creates the file upload markup. I didn't create a corresponding
+# This class creates the file upload markup. I didn't create a corresponding
 # model class here. I may in the future
 class window.sirius.FileUploadView extends Backbone.View
   $a = window.sirius
   tagName: "input"
-  type: "file"
+  type: "file"

   initialize: (args) ->
     $(@el).attr 'type', 'file'
@@ -12,13 +12,13 @@
     @parent = args.attach
     @events = {'change' : 'handleFiles'}
     @render()
-
+
   render: ->
     $(@parent).append(@el)
     @

   # This function is File upload handler. It will load
-  # the xml file, parse it into objects, assign it to window.textarea_scenario,
+  # the xml file, parse it into objects, assign it to window.textarea_scenario,
   # and trigger an event indicating the upload is complete
   handleFiles : ->
     $a.broker.trigger('map:clear_map')
@@ -27,9 +27,8 @@
     reader.onloadend = (e) ->
       fileText = e.target.result
       $a.broker.trigger('map:upload_complete', fileText)
-
+
     reader.readAsText(@el.files[0])
     # if you don't reset the value to '' on the input file tag it won't register
     # a change event if you pick the same file two times in a row
-    @$el.attr('value', '')
- +    @$el.attr('value', '')


Remove useless code

Remove useless code

--- app/controllers/caches-by-branch.coffee
+++ app/controllers/caches-by-branch.coffee
@@ -22,5 +22,4 @@
           model = @get('model')
           @get('controllers.caches').removeObject(model)

-Travis.CachesByBranchController = Controller
 `export default Controller`


Fat arrow requied

Fat arrow requied

in ghci-modi process.onExit

--- lib/ghc-modi-process.coffee
+++ lib/ghc-modi-process.coffee
@@ -45,7 +45,7 @@
     proc = CP.spawn(modiPath,[],@processOptions(rootDir))
     proc.on 'stderr', (data) ->
       console.error 'Ghc-modi says:',data
-    proc.on 'exit', (code) ->
+    proc.on 'exit', (code) =>
       @processMap.delete(rootDir)
       @spawnProcess(rootDir) if code!=0
     @processMap.set(rootDir,proc)


available in any source file

available in any source file

--- lib/ricksum-provider.coffee
+++ lib/ricksum-provider.coffee
@@ -9,7 +9,7 @@
     rightLabel: 'Lorem Ricksum'

 module.exports =
-  selector: '.source.coffee'
+  selector: '.source'
   getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix}) ->
     new Promise (resolve) ->
       matcher = matchers.find (val) -> val.indexOf(prefix) >= 0


work in progres

work in progres

--- lib/web/js/ki-router.coffee
+++ lib/web/js/ki-router.coffee
@@ -56,7 +56,10 @@
   debug: false
   log: =>
     if @debug && console && console.log
-      console.log(arguments)
+      if JSON.stringify
+        console.log("ki-router: " + JSON.stringify(arguments))
+      else
+        console.log(arguments)
   add: (urlPattern, fn, metadata) =>
     @routes.push({route: new SinatraRouteParser(urlPattern), fn: fn, urlPattern: urlPattern, metadata: metadata})
   exec: (path) =>
@@ -151,11 +154,26 @@

   targetHostSame: (aTag) =>
     l = window.location
+    targetUserName = @fixUsername(aTag.username)
+    targetPort = @fixTargetPort(aTag.port, aTag.protocol)
+    aTag.hostname == l.hostname && targetPort == l.port && aTag.protocol == l.protocol && targetUserName == l.username && aTag.password == aTag.password
+
+  fixUsername: (username) =>
     # Firefox 26 sets aTag.username to "", other browsers use undefined
-    targetUserName = aTag.username
-    if targetUserName == ""
-      targetUserName = undefined
-    aTag.host == l.host && aTag.protocol == l.protocol && targetUserName == l.username && aTag.password == aTag.password
+    if username == ""
+      undefined
+    else
+      username
+
+  # IE9 sets port to "443" even if protocol is https
+  fixTargetPort: (port, protocol) =>
+    protocolPorts =
+      "http": "80"
+      "https": "443"
+    if port != "" && port == protocolPorts[protocol]
+      ""
+    else
+      port

   attachLocationChangeListener: =>
     if @pushStateSupport


Save sorted values.

Save sorted values.

The controller saves the positions based on a first come first serve
basis, we just need to make sure we send them in the order we want them.

--- app/assets/javascripts/spree/backend/product_properties.js.coffee
+++ app/assets/javascripts/spree/backend/product_properties.js.coffee
@@ -49,7 +49,7 @@
     @properties = []

     categoryEditorTemplate = _.template($("#category-editor-template").html())
-    @node = $("

").html(categoryEditorTemplate(category_editor: this))
+    @node = $("

").html(categoryEditorTemplate(category_editor: this))
     @parent.append @node

     @delete_button = @node.find '.js-delete-category'
@@ -68,6 +68,10 @@

     @delete_button.click =>
       @parent.trigger 'deleteCategory', this
+
+    @node.on "updateProperties", (event) =>
+      @properties = _.sortBy @properties, (property) ->
+        property.node.find(".js-position").val()

   name: ->
     @node.find(".js-cat-name").val()
@@ -106,13 +110,26 @@
     { key: @key(), value: @value() }

 $('table.js-category-table').ready ->
-  td_count = $(@).find('tbody tr:first-child').first().children().length
+  td_count = $(this).find('tbody tr:first-child').first().children().length
   $('table.js-category-table tbody').sortable
     handle: '.handle',
     helper: (event, ui) ->
       ui.children().each ->
-        $(@).width($(@).width())
+        $(this).width($(this).width())
       return ui
+    update: ->
+      rows = $(this).find("tr")
+      $.each rows, (index, row) =>
+        if $(row).find(".js-position").length > 0
+          $(row).find(".js-position").attr("value", index)
+        else
+          input = $("<input />").attr
+            type: "hidden",
+            class: "js-position",
+            value: index
+          input.appendTo(row)
+
+      $(this).closest(".js-category-node").trigger "updateProperties"
     placeholder: 'ui-sortable-placeholder',
     start: (event, ui) ->
       ui.placeholder.height(ui.item.height())


arreglos (gracias Fabricio)

arreglos (gracias Fabricio)

--- app/assets/javascripts/views/clientes/clientes_new_view.js.coffee
+++ app/assets/javascripts/views/clientes/clientes_new_view.js.coffee
@@ -2,7 +2,6 @@
   tagName: "form"
   tiposIds: ["RUC", "Cédula", "Otro"]

-<<<<<<< HEAD
   success: ->
     console.log "success"

@@ -15,8 +14,4 @@
     new_cliente = store.createRecord "cliente", data
     new_cliente.save().then @success, @failure
     @set "submitting", true
-=======
-  submit: ->
-    console.log "submit!"
->>>>>>> 31691c72985ba0d5f12b512e39f6311a2e0e6eb9
     false


Use jquery ajax for submitting browser error

--- app/javascript/src/legacy/common.coffee
+++ app/javascript/src/legacy/common.coffee
@@ -807,9 +807,7 @@
   document.cookie = 'reported_error=1; path=/; expires=' + expiration.toGMTString()
   report = FormatError((if exc then exc.message else message), file, line, exc, info)
   try
-    new (Ajax.Request)('/user/error.json',
-      requestHeaders: 'X-CSRF-Token': jQuery('meta[name=csrf-token]').attr('content')
-      parameters: report: report)
+    jQuery.post '/user/error.json', report: report
   catch e
     alert 'Error: ' + e
   return


fixed bug

fixed bug

--- app/lib/collections/schemas/user.coffee
+++ app/lib/collections/schemas/user.coffee
@@ -7,6 +7,7 @@
   services:
     type: Object
     blackbox: true
+    optional: true
   emails:
     type: [Object]
     label: 'Emails'
@@ -15,12 +16,19 @@
     type: String
   'emails.$.verified':
     type: Boolean
+    optional: true
+    defaultValue: false
   profile:
     type: schemas.UserProfile
     label: 'Profile'
   createdAt:
     type: Date
     label: 'Created at'
+    denyUpdate: true
+    autoform:
+      type: 'hidden'
+    autoValue: () ->
+      new Date() if @isInsert
   roles:
     type: [String]
     label: 'Roles'


fixed react style warning

fixed react style warning

--- app/pages/lab/workflow-viewer/task.cjsx
+++ app/pages/lab/workflow-viewer/task.cjsx
@@ -245,7 +245,7 @@
         required_box = undefined

     inputs = @getInputs()
-    <Resizable className={box_class} style={@state.style} id={@props.plumbId} ref={@props.plumbId} embedCss={false} onResize={@onResize}>
+    <Resizable className={box_class} style={clone(@state.style)} id={@props.plumbId} ref={@props.plumbId} embedCss={false} onResize={@onResize}>

           {"Sub-" if @state.task.subtask}{"Task #{@props.taskKey.substr(1)}: #{@state.task.type.charAt(0).toUpperCase()}#{@state.task.type.substr(1)}"}
@@ -331,6 +331,10 @@

 #

+# A function to clone JSON object
+clone = (obj) ->
+  return JSON.parse(JSON.stringify(obj))
+
 module.exports = {
   Task
   StartEndNode


Pass a ProgressState instance to bar.tick() directly

--- lib/actions/os.coffee
+++ lib/actions/os.coffee
@@ -67,7 +67,7 @@
 				bar = new visuals.widgets.Progress('Downloading Device OS')

 				resin.models.os.download osParams, options.output, callback, (state) ->
-					console.log(bar.tick(state.percentage, state.eta))
+					console.log(bar.tick(state))

 		], (error) ->
 			return done(error) if error?
@@ -132,7 +132,7 @@
 					bar = new visuals.widgets.Progress('Writing Device OS')

 					progress.on 'progress', (status) ->
-						console.log(bar.tick(status.percentage, status.eta))
+						console.log(bar.tick(status))

 				imageFileStream = fs.createReadStream(params.image).pipe(progress)


make token checking faster

make token checking faster

--- lib/authDevice.coffee
+++ lib/authDevice.coffee
@@ -19,7 +19,8 @@
       bcrypt.compare token, hashedToken, (error, result) =>
         callback(result)

-    async.detect hashedTokens, compareToken, (goodToken) =>
+    # this is faster than async.detect, srsly, trust me.
+    async.detectSeries hashedTokens.reverse(), compareToken, (goodToken) =>
       debug 'token matched?', goodToken?
       return callback null, device if goodToken?
       return callback null, null


Major refactoring

Major refactoring

Only attach a behaviour once.
Support attaching multiple behaviours with and without options.

--- lib/behaviours.coffee
+++ lib/behaviours.coffee
@@ -1,24 +1,109 @@
-behaviours = {}
+definedBehaviours = {}

-share.attach = attach = (behaviour, args...) ->
-  check behaviour, Match.OneOf Function, String
+messages =
+  attachAborted: (name, collectionName) ->
+    "Attach aborted, behaviour \"#{name}\" already attached to" +
+    " #{collectionName} collection"
+  attachFailed: (name) ->
+    "Attach failed, behaviour \"#{name}\" not found"

-  if Match.test behaviour, String
-    behaviour = behaviour.toLowerCase()
-    options = behaviours[behaviour]?.options
-    behaviour = behaviours[behaviour]?.behaviour
+share.attach = attach = (behaviours, options...) ->
+  check behaviours, Match.OneOf Function, [Match.OneOf Function, String], Object
+  , String

-  if Match.test behaviour, Function
+  if Match.test behaviours, String
+    name = behaviours.toLowerCase()
+    behaviourObject = definedBehaviours[name]
+
+    unless behaviourObject
+      console.error messages.attachFailed name
+      return
+
+    behaviourObject.collections ?= []
+
+    if @_name in behaviourObject.collections
+      console.warn messages.attachAborted name, @_name
+      return
+
+    behaviours = behaviourObject.behaviour
+
+  if Match.test behaviours, Function
     context =
       collection: @
-      options: options or {}
+      options: behaviourObject?.options or {}

-    behaviour.apply context, args
+    behaviours.apply context, options

-  else
-    console.error "Attach failed, behaviour \"#{behaviour}\" not found"
+    behaviourObject?.collections ?= []
+    behaviourObject?.collections.push @_name

-  return
+    return
+
+  if Match.test behaviours, [Match.OneOf Function, String]
+    context =
+      collection: @
+      options: {}
+
+    for behaviour in behaviours
+      if Match.test behaviour, String
+        name = behaviour.toLowerCase()
+        behaviourObject = definedBehaviours[name]
+
+        unless behaviourObject
+          console.error messages.attachFailed name
+          continue
+
+        behaviourObject.collections ?= []
+
+        if @_name in behaviourObject.collections
+          console.warn messages.attachAborted name, @_name
+          continue
+
+        behaviour = behaviourObject.behaviour
+
+      if Match.test behaviour, Function
+        behaviour.apply context, {}
+
+        behaviourObject?.collections ?= []
+        behaviourObject?.collections.push @_name
+
+    return
+
+  if Match.test behaviours, Object
+    for name, options of behaviours
+      check name, String
+
+      name = name.toLowerCase()
+
+      behaviourObject = definedBehaviours[name]
+
+      unless behaviourObject
+        console.error messages.attachFailed name
+        continue
+
+      behaviourObject.collections ?= []
+
+      if @_name in behaviourObject.collections
+        console.warn messages.attachAborted name, @_name
+        continue
+
+      behaviour = behaviourObject.behaviour
+
+      context =
+        collection: @
+        options: behaviourObject.options or {}
+
+      if Match.test behaviour, Function
+        behaviour.call context, options
+
+        behaviourObject.collections.push @_name
+
+      else
+        console.error messages.attachFailed name
+
+    return
+
+  console.error "Attach failed, unknown reason"

 CollectionBehaviours =

@@ -41,8 +126,10 @@

     name = name.toLowerCase()

-    if name of behaviours
-      behaviours[name].options = options
+    behaviourObject = definedBehaviours[name]
+
+    if behaviourObject
+      behaviourObject.options = options

     else
       console.error "Configure failed, behaviour \"#{name}\" not found"
@@ -58,9 +145,10 @@

     name = name.toLowerCase()

-    if name of behaviours and not options?.replace
+    behaviourObject = definedBehaviours[name]
+
+    if behaviourObject and not options?.replace
       console.warn 'Behaviour already defined, use {replace: true} to override'

     else
-      behaviours[name] =
-        behaviour: behaviour
+      definedBehaviours[name] = behaviour: behaviour


Perform save and destroy through backbone model

--- backend/app/assets/javascripts/spree/backend/line_items.js.coffee
+++ backend/app/assets/javascripts/spree/backend/line_items.js.coffee
@@ -1,28 +1,3 @@
-
-lineItemURL = (id) ->
-  "#{Spree.routes.line_items_api(order_number)}/#{id}.json"
-
-adjustLineItem = (line_item_id, quantity) ->
-  url = lineItemURL(line_item_id)
-  Spree.ajax(
-    type: "PUT",
-    url: url,
-    data:
-      line_item:
-        quantity: quantity
-  ).done (msg) ->
-    window.Spree.advanceOrder()
-
-deleteLineItem = (line_item_id) ->
-  url = lineItemURL(line_item_id)
-  Spree.ajax(
-    type: "DELETE"
-    url: url
-  ).done (msg) ->
-    $('#line-item-' + line_item_id).remove()
-    if $('.line-items tr.line-item').length == 0
-      $('.line-items').remove()
-    window.Spree.advanceOrder()

 Spree.CartLineItemView = Backbone.View.extend
   tagName: 'tr'
@@ -44,17 +19,20 @@

   onSave: (e) ->
     e.preventDefault()
-    line_item_id = @model.id
     quantity = parseInt(@$('input.line_item_quantity').val())
-    adjustLineItem(line_item_id, quantity)
+    @model.save {quantity: quantity},
+      patch: true,
+      success: =>
+        window.Spree.advanceOrder()
     @$el.removeClass('editing')

   onDelete: (e) ->
     e.preventDefault()
     return unless confirm(Spree.translations.are_you_sure_delete)
-    line_item_id = @model.id
-    deleteLineItem(line_item_id)
-    @$el.removeClass('editing')
+    @remove()
+    @model.destroy
+      success: =>
+        window.Spree.advanceOrder()

   render: ->
     line_item = @model.attributes


Check if routes exists before iterating

Check if routes exists before iterating

--- src/middleware/stats.coffee
+++ src/middleware/stats.coffee
@@ -19,9 +19,10 @@
     sdc.increment domain + '.Channels.' + ctx.authorisedChannel._id + '.' + transactionStatus # Per Channel Status

     #Collect stats for non-primary routes
-    for route in ctx.routes
-      sdc.increment domain + '.Channels.' + ctx.authorisedChannel._id + '.' + route.name # Per non-primary route
-      sdc.increment domain + '.Channels.' + ctx.authorisedChannel._id + '.' + route.name + '.' + route.response.status # Per route response status
+    if ctx.routes?
+      for route in ctx.routes
+        sdc.increment domain + '.Channels.' + ctx.authorisedChannel._id + '.' + route.name # Per non-primary route
+        sdc.increment domain + '.Channels.' + ctx.authorisedChannel._id + '.' + route.name + '.' + route.response.status # Per route response status

   catch error
     logger.error error
@@ -37,9 +38,10 @@
     sdc.timing domain + '.Channels.' + ctx.authorisedChannel._id + '.' + transactionStatus, timer #Per Channel Status

     #Collect stats for non-primary routes
-    for route in ctx.routes
-      sdc.timing domain + '.Channels.' + ctx.authorisedChannel._id + '.' + route.name, timer # Per Channel
-      sdc.timing domain + '.Channels.' + ctx.authorisedChannel._id + '.' + route.name + '.' + route.response.status, timer # Per Channel
+    if ctx.routes?
+      for route in ctx.routes
+        sdc.timing domain + '.Channels.' + ctx.authorisedChannel._id + '.' + route.name, timer # Per Channel
+        sdc.timing domain + '.Channels.' + ctx.authorisedChannel._id + '.' + route.name + '.' + route.response.status, timer # Per Channel

   catch error
     logger.error error


Markup touchups in the error dialog.

Markup touchups in the error dialog.

--- lib/error-view.coffee
+++ lib/error-view.coffee
@@ -4,17 +4,19 @@
 class GitNotFoundErrorView extends View

   @content: (err) ->
-    @div class: 'overlay from-top padded merge-conflict-error', =>
-      @div class: 'block error-message', =>
-        @code 'git'
-        @text "can't be found at "
-        @code atom.config.get 'merge-conflicts.gitPath'
-        @text '!'
-      @div class: 'block',
-        'Please fix the path in your settings.'
-      @div class: 'block', =>
-        @button class: 'btn btn-primary inline-block-tight', click: 'openSettings', 'Open Settings'
-        @button class: 'btn inline-block-tight', click: 'notRightNow', 'Not Right Now'
+    @div class: 'overlay from-top padded merge-conflict-error merge-conflicts-message', =>
+      @div class: 'panel', =>
+        @div class: "panel-heading text-error", =>
+          @code 'git'
+          @text "can't be found at "
+          @code atom.config.get 'merge-conflicts.gitPath'
+          @text '!'
+        @div class: 'panel-body', =>
+          @div class: 'block',
+            'Please fix the path in your settings.'
+          @div class: 'block', =>
+            @button class: 'btn btn-error inline-block-tight', click: 'openSettings', 'Open Settings'
+            @button class: 'btn inline-block-tight', click: 'notRightNow', 'Not Right Now'

   openSettings: ->
     atom.workspace.open 'atom://config'


hconfigure: promise

hconfigure: promise

--- lib/hconfigure.coffee
+++ lib/hconfigure.coffee
@@ -78,9 +78,13 @@
       # options.log? "Save properties"
       options.content = properties.stringify fnl_props
       options.source = null
-      ctx.write options, (err, written) ->
-        updated = true if written
-        callback err, updated
+      # ctx.write options, (err, written) ->
+      #   updated = true if written
+      #   callback err, updated
+      ctx
+      .write options
+      .then (err, status) ->
+        callback err, status
     do_read_source()


:lipstick: Cleanup.

:lipstick: Cleanup.

- Rearranging order of methods.

--- lib/jumpy-view.coffee
+++ lib/jumpy-view.coffee
@@ -40,45 +40,9 @@
         @firstChar = null
         @secondChar = null

-    clearJumpMode: ->
-        @clearKeys()
-        $('#status-bar-jumpy').html("")
-        atom.workspaceView.eachEditorView (e) ->
-            e.find('.jumpy').remove()
-            e.removeClass 'jumpy-jump-mode'
-        atom.keymap.keyBindings = @backedUpKeyBindings
-        @detach()
-
-    jump: ->
-        location = @findLocation()
-        if location == null
-            console.log "Jumpy canceled jump.  No location found."
-            return
-        atom.workspaceView.eachEditorView (editorView) =>
-            currentEditor = editorView.getEditor()
-            if currentEditor.id == location.editor
-                pane = editorView.getPane()
-                pane.activate()
-                currentEditor.setCursorBufferPosition(location.position)
-                console.log "Jumpy jumped to: #{@firstChar}#{@secondChar} at " +
-                    "(#{location.position.row},#{location.position.column})"
-
-    findLocation: ->
-        label = "#{@firstChar}#{@secondChar}"
-        if label of @allPositions
-            return @allPositions[label]
-
-        return null
-
-    # Returns an object that can be retrieved when package is activated
-    serialize: ->
-
-    # Tear down any state and detach
-    destroy: ->
-        console.log 'Jumpy: "destroy" called. Detaching.'
+    clear: ->
         @clearJumpMode()
-        @detach()
-
+
     turnOffSlowKeys: ->
         atom.keymap.keyBindings = atom.keymap.keyBindings.filter (keymap) ->
             keymap.command.indexOf('jumpy') > -1
@@ -126,5 +90,41 @@
                     $labels
                         .append labelElement

-    clear: ->
+    clearJumpMode: ->
+        @clearKeys()
+        $('#status-bar-jumpy').html("")
+        atom.workspaceView.eachEditorView (e) ->
+            e.find('.jumpy').remove()
+            e.removeClass 'jumpy-jump-mode'
+        atom.keymap.keyBindings = @backedUpKeyBindings
+        @detach()
+
+    jump: ->
+        location = @findLocation()
+        if location == null
+            console.log "Jumpy canceled jump.  No location found."
+            return
+        atom.workspaceView.eachEditorView (editorView) =>
+            currentEditor = editorView.getEditor()
+            if currentEditor.id == location.editor
+                pane = editorView.getPane()
+                pane.activate()
+                currentEditor.setCursorBufferPosition(location.position)
+                console.log "Jumpy jumped to: #{@firstChar}#{@secondChar} at " +
+                    "(#{location.position.row},#{location.position.column})"
+
+    findLocation: ->
+        label = "#{@firstChar}#{@secondChar}"
+        if label of @allPositions
+            return @allPositions[label]
+
+        return null
+
+    # Returns an object that can be retrieved when package is activated
+    serialize: ->
+
+    # Tear down any state and detach
+    destroy: ->
+        console.log 'Jumpy: "destroy" called. Detaching.'
         @clearJumpMode()
+        @detach()


Some performance and cleanup.

Some performance and cleanup.

--- lib/jumpy-view.coffee
+++ lib/jumpy-view.coffee
@@ -8,6 +8,8 @@
 for c1 in characters
     for c2 in characters
         keys.push c1 + c2
+
+wordsPattern = /([\w]){2,}/g

 module.exports =
 class JumpyView extends View
@@ -83,6 +85,7 @@
             keymap.command.indexOf('jumpy') > -1

     toggle: ->
+        console.time('toggle')
         @turnOffSlowKeys()
         $('#status-bar-jumpy').html "Jumpy: Jump Mode!"
         @allPositions = {}
@@ -91,7 +94,7 @@
         atom.workspaceView.eachEditorView (editorView) ->
             return if !editorView.active
             editorView.addClass 'jumpy-jump-mode'
-            editorView.find '.scroll-view .overlayer'
+            $labels = editorView.find '.scroll-view .overlayer'
                 .append "

"

             atom.workspaceView.find '*'
@@ -101,7 +104,6 @@
             isScreenRowVisible = (lineNumber) ->
                 return lineNumber > editorView.getFirstVisibleScreenRow() &&
                     lineNumber < editorView.getLastVisibleScreenRow()
-            wordsPattern = /([\w]){2,}/g
             for line, lineNumber in editorView.getEditor().buffer.lines
                 if line != ''
                     while ((word = wordsPattern.exec(line)) != null)
@@ -117,14 +119,14 @@
                                 .pixelPositionForBufferPosition [lineNumber,
                                 word.index]
                             labelElement =
-                                $("
#{keyLabel}
")
+                                $("
#{keyLabel}
")
                                     .css {
                                         left: pixelPosition.left
                                         top: pixelPosition.top
                                     }
-                                    .addClass 'jump'
-                            editorView.find ".jumpy.labels"
+                            $labels
                                 .append labelElement
+        console.timeEnd('toggle')

     clear: ->
         @clearJumpMode()


Fix clear filters button

Fix clear filters button

--- app/assets/javascripts/active_admin/application.js.coffee
+++ app/assets/javascripts/active_admin/application.js.coffee
@@ -13,7 +13,7 @@

   # Clear Filters button
   $('.clear_filters_btn').click ->
-    params = window.location.search.split('&')
+    params = window.location.search.slice(1).split('&')
     regex = /^(q\[|q%5B|q%5b|page|commit)/
     window.location.search = (param for param in params when not param.match(regex)).join('&')


Delete for schedulers

Delete for schedulers

--- app/assets/javascripts/admin/routers/schedulers.js.coffee
+++ app/assets/javascripts/admin/routers/schedulers.js.coffee
@@ -6,6 +6,7 @@
     "schedulers": "index"
     "schedulers/new": "new"
     "schedulers/:id/edit": "edit"
+    "schedulers/delete/:id?confirm=:confirm": "delete"

   index: (integration_id) ->
     Augury.update_nav('schedulers')
@@ -29,4 +30,15 @@
     scheduler = @collection.get(id)
     view = new Augury.Views.Schedulers.Edit(model: scheduler, keys: Augury.keys)
     $("#integration_main").html view.render().el
+
+  delete: (id, confirm) ->
+    scheduler = @collection.get(id)
+    if confirm != 'true'
+      dialog = JST['admin/templates/shared/confirm_delete']
+      $.modal(dialog(klass: 'schedulers', warning: 'Are you sure you want to delete this Scheduler?', identifier: id))
+    else
+      $.modal.close()
+      scheduler.destroy()
+      Augury.schedulers.remove scheduler
+      Backbone.history.navigate '/schedulers', trigger: true
 )


Return all users on small clouds.

Return all users on small clouds.

--- app/assets/javascripts/collections/clouds/users.js.coffee
+++ app/assets/javascripts/collections/clouds/users.js.coffee
@@ -1,7 +1,11 @@
 class Cloudsdale.Collections.CloudsUsers extends Backbone.Collection

   model: Cloudsdale.Models.User
-  url: -> "/v1/#{@topic.type}s/#{@topic.id}/users/online.json"
+  url: ->
+    if @topic.get('member_count') > 100
+      return "/v1/#{@topic.type}s/#{@topic.id}/users/online.json"
+    else
+      return "/v1/#{@topic.type}s/#{@topic.id}/users.json"

   topic: 'cloud'


reverting one change back

reverting one change back

--- app/assets/javascripts/components/subject-set-viewer.cjsx
+++ app/assets/javascripts/components/subject-set-viewer.cjsx
@@ -38,8 +38,8 @@
     console.log " SSV Render @state", @state

-        <ActionButton text="Previous" onClick={@advancePrevious} className={if @props.subject_set_index == 0 then 'disabled' else ''}/>
-        <ActionButton text="Next" onClick={@advanceNext} className={if @props.subject_set_index == @props.subject_set.subjects.length-1 then 'disabled' else ''} />
+        <ActionButton text="Previous" onClick={@advancePrevious} className={if @state.subject_set_index == 0 then 'disabled' else ''}/>
+        <ActionButton text="Next" onClick={@advanceNext} className={if @state.subject_set_index == @props.subject_set.subjects.length-1 then 'disabled' else ''} />

       { for subject, index in @props.subject_set.subjects
         console.log "SSV subject", subject
@@ -49,7 +49,7 @@
           workflow={@props.workflow}
           task={@props.task}
           annotation={@props.annotation}
-          active={index == @props.subject_set_index}
+          active={index == @state.subject_set_index}
           onComplete={@props.onComplete}
           onChange={@props.onChange}
         />


#1436 Fix rendering bug

#1436 Fix rendering bug

--- app/assets/javascripts/oxalis/model/binary/plane2d.coffee
+++ app/assets/javascripts/oxalis/model/binary/plane2d.coffee
@@ -273,7 +273,7 @@
         {
           buffer: @dataTexture.buffer
           offset: bufferOffsetByTileMacro(@, tile, tileSizeP)
-          widthP: @cube.BUCKET_SIZE_P
+          widthP: tileSizeP
           rowDelta: 1 << @TEXTURE_SIZE_P
         }
         {


call .data() methods when sending json

call .data() methods when sending json

--- app/express.coffee
+++ app/express.coffee
@@ -48,11 +48,16 @@
     app.locals.pretty = true
     app.use reply (opts, req) ->
       format = req.param 'format'
-      # Send argumenta object instances as plain object data
+      # Send argumenta objects with data() methods as plain object data
       if format is 'json' or format is 'jsonp'
         for key, val of opts
-          if val instanceof Objects.Argument
-            opts[key] = val.data()
+          switch typeof val
+            when 'object'
+              if typeof val.data is 'function'
+                opts[key] = val.data()
+            when 'array'
+              if 'function' is typeof val[0]?.data
+                opts[key] = val.map (obj) -> obj.data()
       return opts
     app.use app.router


Remove old view

Remove old view

--- assets/javascripts/fetch_pool.js.coffee
+++ assets/javascripts/fetch_pool.js.coffee
@@ -1,20 +0,0 @@
-class TentStatus.FetchPool extends TentStatus.Paginator
-  constructor: (@collection, @options = {}) ->
-    super
-    @sinceId = @options.sinceId
-    @options.is_background_operation = true
-
-  paramsForOffsetAndLimit: (since_id_entity, sinceId, limit) =>
-    params = _.extend { limit: limit }, @default_params
-    params.since_id = sinceId if sinceId
-    params.since_id_entity = since_id_entity if since_id_entity
-    params
-
-  fetch: (options = {}) =>
-    super(_.extend {
-      n_pages: 'infinite'
-    }, options)
-
-  filterNewItems: (items, collection=@collection) =>
-    super(super(items, collection), @options.master_collection)
-


added failing test for #4

added failing test for #4

--- test/page.coffee
+++ test/page.coffee
@@ -87,6 +87,16 @@

           "and have those clicks register": (clicked) ->
             assert.ok clicked
+
+        "can register an onConsoleMessage handler":
+          topic: t (page) ->
+            test = this
+            page.set 'onConsoleMessage', (msg) -> test.callback null, msg
+            page.evaluate (-> console.log "Hello, world!")
+
+          "which works correctly": (msg) ->
+            assert.equal msg, "Hello, world!"
+

     teardown: (page, ph) ->
       ph.exit()


fixed experience_modal not saving

fixed experience_modal not saving

--- app/assets/javascripts/admin/views/user/team_role_modal_view.coffee
+++ app/assets/javascripts/admin/views/user/team_role_modal_view.coffee
@@ -68,7 +68,7 @@
               role :
                 name: @$("select[data-teamname=\"#{teamName}\"] :selected").val()
             }
-          ) || []
+          , @) || []

           # Find unselected teams
           removedTeamsNames = _.map(@$("input[type=checkbox]:not(:checked)"), (element) ->


remove hide navibar

remove hide navibar

--- app/assets/javascripts/app/controllers/retrospective_ctrl.js.coffee
+++ app/assets/javascripts/app/controllers/retrospective_ctrl.js.coffee
@@ -11,20 +11,10 @@
       $scope.users = []
       $scope.url = jQuery('.retrospective-ctrl').data('url')
       $scope.id = jQuery('.retrospective-ctrl').data('id')
-      $scope.initNavBar()
       $scope.initLabelForm()
       $scope.initSortable()
       $scope.initWebSocket()
       $scope.labelForm = new app.LabelForm('#label-form', save: $scope.save)
-
-    $scope.initNavBar =->
-      showNavBar = true
-      jQuery('.page-header').click ->
-        showNavBar^= true
-        jQuery('.navbar').toggle()
-        jQuery('body').css('padding-top': if showNavBar then 60 else 0)
-        jQuery('.page-header').css('margin-top' : if showNavBar then 40 else 0)
-        jQuery('.page-header h1').css('margin-top' : if showNavBar then 20 else 0)

     $scope.start = (e, ui) ->
       start =


added some comments

added some comments

--- source/javascripts/app/controllers/application_controller.js.coffee
+++ source/javascripts/app/controllers/application_controller.js.coffee
@@ -7,14 +7,17 @@
     filteredShow = @get('filteredShow').toLowerCase()

     # Get contents of 'Shows' controller
+    # ToDo: This is depricated in Ember
     shows = @controllerFor('shows').get('arrangedContent')

     unless Em.isEmpty(filteredShow)
+      # Mark each show for filtering
       shows.forEach (show) ->
         if show.get('show.title').toLowerCase().indexOf(filteredShow) >= 0
           show.set 'filtered', true
         else
           show.set 'filtered', false
+    # Search box is empty
     else
       shows.forEach (show) ->
         show.set 'filtered', false


:bug:Fix InspectletJS script loading

:bug:Fix InspectletJS script loading

Original embed code was not correct for delayed script loading.

--- app/core/Tracker.coffee
+++ app/core/Tracker.coffee
@@ -19,35 +19,27 @@
     # InspectletJS loading is delayed and targeting specific levels for more focused investigations
     return @disableInspectletJS() unless levelSlug in targetInspectJSLevelSlugs

-    # Start embed code
-    window.__insp = window.__insp or []
-    __insp.push [
-      'wid'
-      2102699786
-    ]
-    do ->
-      __ldinsp = ->
-        insp = document.createElement('script')
-        insp.type = 'text/javascript'
-        insp.async = true
-        insp.id = 'inspsync'
-        insp.src = (if 'https:' == document.location.protocol then 'https' else 'http') + '://cdn.inspectlet.com/inspectlet.js'
-        x = document.getElementsByTagName('script')[0]
-        x.parentNode.insertBefore insp, x
-        return
-
-      if window.attachEvent
-        window.attachEvent 'onload', __ldinsp
-      else
-        window.addEventListener 'load', __ldinsp, false
-    # End embed code
-
-    # Identify and track pageview here, because inspectlet is loaded too late for standard Tracker calls
-    @identify()
-    # http://www.inspectlet.com/docs#virtual_pageviews
-    __insp?.push ['virtualPage']
+    scriptLoaded = =>
+      # Identify and track pageview here, because inspectlet is loaded too late for standard Tracker calls
+      @identify()
+      # http://www.inspectlet.com/docs#virtual_pageviews
+      window.__insp?.push(['virtualPage'])
+    window.__insp = [['wid', 2102699786]]
+    insp = document.createElement('script')
+    insp.type = 'text/javascript'
+    insp.async = true
+    insp.id = 'inspsync'
+    insp.src = (if 'https:' == document.location.protocol then 'https' else 'http') + '://cdn.inspectlet.com/inspectlet.js'
+    insp.onreadystatechange = => scriptLoaded() if insp.readyState is 'complete'
+    insp.onload = scriptLoaded
+    x = document.getElementsByTagName('script')[0]
+    @inspectletScriptNode = x.parentNode.insertBefore insp, x

   disableInspectletJS: ->
+    if @inspectletScriptNode
+      x = document.getElementsByTagName('script')[0]
+      x.parentNode.removeChild(@inspectletScriptNode)
+      @inspectletScriptNode = null
     delete window.__insp

   trackReferrers: ->


Fixed timestamp not properly set when adding tasks from presets

--- presets.coffee
+++ presets.coffee
@@ -2,7 +2,6 @@
 common = require './common'
 db = require './db'
 mongoQuery = require './mongo-query'
-mongodb = require 'mongodb'
 query = require './query'

@@ -90,9 +89,9 @@
             throw new Error('Unknown configuration type')

       if getParameterValues.length
-        taskList.push {device : deviceId, name : 'getParameterValues', parameterNames: getParameterValues, timestamp : mongodb.Timestamp()}
+        taskList.push {device : deviceId, name : 'getParameterValues', parameterNames: getParameterValues, timestamp : new Date()}
       if setParameterValues.length
-        taskList.push {device : deviceId, name : 'setParameterValues', parameterValues: setParameterValues, timestamp : mongodb.Timestamp()}
+        taskList.push {device : deviceId, name : 'setParameterValues', parameterValues: setParameterValues, timestamp : new Date()}

       if not presetsHash
         presetsHash = calculatePresetsHash(presets)


make withContent test helper to set the content used for the tests

--- test/test.coffee
+++ test/test.coffee
@@ -1,34 +1,32 @@
+withContent = (content)->
+  if $("#content").length == 0
+    $('body').append('

')
+  $("#content").html(content)
+  $(document).trigger("page:change")

 test "the consumer reads the url from the meta tag", ->
   equal "ws://localhost:28080", Cable.boundConsumer.url,

 test "it should create subscritions for data-cable-subscribe attributes in the dom", ->
-  $('body').append("

")
-  $(document).trigger("page:change")
+  withContent("

")

   equal Cable.boundConsumer.subscriptions.findAll("{\"channel\":\"ChatChannel\"}").length, 1

-  $("[data-cable-subscribe=ChatChannel]").remove()
-  $(document).trigger("page:change")
-
+  withContent("")
   equal Cable.boundConsumer.subscriptions.findAll("{\"channel\":\"ChatChannel\"}").length, 0

 test "it should trigger events on the elements for messages received through the channel", ->
-  $('body').append("

")
-  $(document).trigger("page:change")
+  withContent("

")
   $('#event-test').on "cable:received", (e, data)->
     deepEqual data, {"message": "Hello"}
-    $('#event-test').remove()

   Cable.boundConsumer.subscriptions.notify("{\"channel\":\"ChatChannel\"}","received", {"message": "Hello"})

 test "it should perform actions on the elements for messages received through the channel", ->
-  $('body').append("
Action
")
-  $(document).trigger("page:change")
+  withContent("
Action
")

   subscription = Cable.boundConsumer.subscriptions.findAll("{\"channel\":\"ChatChannel\"}")[0]
   subscription.perform = (action, data)->
     equal action, 'doAction'
-    $('#action-test').remove()

   $('[data-cable-action]').click()


msLogger -> mpLogger

msLogger -> mpLogger

--- test/test.coffee
+++ test/test.coffee
@@ -1,4 +1,4 @@
-msLogger   = require '../'
+mpLogger   = require '../'
 ms         = require 'message-ports'
 {testCase} = require 'nodeunit'

@@ -7,13 +7,13 @@
   "connect message sends whole buffer": (test) ->
     test.expect 1

-    logger = msLogger 'test'
+    logger = mpLogger 'test'

     logger.info "one"
     logger.info "two"
     logger.info "three"

-    req = ms.req 'ipc:///tmp/mslogger/test'
+    req = ms.req 'ipc:///tmp/mplogger/test'
     req { msg: 'connect' }, (res) ->
       test.strictEqual res.messages.length, 3

@@ -22,7 +22,7 @@
       test.done()

   "a null config returns a noop logger": (test) ->
-    logger = msLogger 'test', null
+    logger = mpLogger 'test', null
     test.doesNotThrow ->
       logger.info "does nothing. goes nowhere."
     test.done()