fixing broken test from previous commit

fixing broken test from previous commit

--- portfoliohierarchy/PortfolioHierarchyAppSpec.coffee
+++ portfoliohierarchy/PortfolioHierarchyAppSpec.coffee
@@ -26,6 +26,9 @@
       @waitForComponentReady(@app)

   beforeEach ->
+
+    Rally.environment.getContext().context.subscription.Modules = ['Rally Portfolio Manager']
+
     @userStoryModel = Rally.test.mock.data.ModelFactory.getUserStoryModel()

     @ajax.whenQuerying('typedefinition').respondWith [


Re-add template id fields to Project model.

The original pull request (/web-sharelatex-internal/pull/324) had to be reverted
because of a failing test. This just re-adds the fields

--- services/web/app/coffee/models/Project.coffee
+++ services/web/app/coffee/models/Project.coffee
@@ -48,6 +48,8 @@
 		}
 	tokenAccessReadOnly_refs         : [ type:ObjectId, ref:'User' ]
 	tokenAccessReadAndWrite_refs     : [ type:ObjectId, ref:'User' ]
+	fromV1TemplateId: { type: Number }
+	fromV1TemplateVersionId: { type: Number }
 	overleaf          :
 		id              : { type: Number }
 		imported_at_ver_id : { type: Number }


Prevented incrementing project name which is no longer unique.

--- src/ProjectUtils.coffee
+++ src/ProjectUtils.coffee
@@ -39,10 +39,6 @@
     df = Q.defer()
     # A map of collection names to maps of model IDs from the input to the new IDs constructed.
     idMaps = {}
-
-    # Increment the name of Projects to ensure they are unique.
-    _.each json[Collections.getName(Projects)], (project) =>
-      project.name = @getNextAvailableName(project.name)

     createDfs = []
     collectionMap = Collections.getMap(CollectionUtils.getAll())


:fire: unused

:fire: unused

--- lib/register-manager.coffee
+++ lib/register-manager.coffee
@@ -1,7 +1,6 @@
 # Refactoring status: 100%
 globalState = require './global-state'
 settings = require './settings'
-{CompositeDisposable} = require 'atom'

 REGISTERS = /// (
   ?: [a-zA-Z*+%_".]


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -21,7 +21,7 @@
   indexSum = 0

   engine.each (obj, index) ->
-    if
+    if index

 test "#find", ->
   engine = Engine()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -19,7 +19,7 @@
     y: 10

   counter = 0
-  indexSu
+  indexSum = 0

   engine.each (obj, index) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -3,7 +3,20 @@
 test "#closest", ->
   engine = Engine()

-
+  obj1 = engine.add
+    rad: "Awesome"
+    x: 0
+    y: 5
+
+  obj2 = engine.add
+    rad: "Awesome"
+    x: 5
+    y: 10
+
+  obj3 = engine.add
+    rad: "Awesome"
+    x: 5
+    y: 10

 test "#each", ->
   engine = Engine()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -4,7 +4,7 @@
   engine = Engine()

   engine.add
-    c
+    x:

 module()


minor change

minor change

--- src/parser.coffee
+++ src/parser.coffee
@@ -77,8 +77,7 @@

   for val, i in arr
     key = val.startTime+','+val.endTime
-    if dict[key] isnt undefined
-      idx = dict[key]
+    if (idx = dict[key]) isnt undefined
       for lang, content of val.languages
         ret[idx].languages[lang] = content
     else


Shakes are awesome

Shakes are awesome

--- source/javascripts/app/views/create_shake_view.coffee
+++ source/javascripts/app/views/create_shake_view.coffee
@@ -4,6 +4,9 @@

   events:
     'click': '_onClick'
+
+  onRender: ->
+    @$el.data(id: @model.id)

   _onClick: ->
     @$el.toggleClass('selected', !@$el.hasClass('selected'))
@@ -20,7 +23,9 @@
     'click .submit-btn': '_onSubmit'

   _onSubmit: ->
-    @model.relation('ingredients').add(@collection.first())
+    ingredientsIds = @$el.find('.ingredient.selected').map((i, el) -> $(el).data('id'))
+    collection = _.map(ingredientsIds, (id) => @collection.get(id))
+    @model.relation('ingredients').add(collection)
     @model.set(name: @ui.nameInput.val())
-    @model.save(null, success: -> alert('yey'))
+    @model.save(null, success: -> window.location.reload())


Fix wording on tests

Fix wording on tests

--- test/primitives-test.coffee
+++ test/primitives-test.coffee
@@ -82,7 +82,7 @@
       it 'has the right attributes', ->
         expect(returnedType.attributes).to.deep.equal el.attributes

-    context 'when give array', ->
+    context 'when given array', ->
       returnedType = undefined
       el = undefined

@@ -109,7 +109,7 @@
       it 'has the correct values', ->
         expect(returnedType.toValue()).to.deep.equal [ 1, 2 ]

-    context 'when give array', ->
+    context 'when given object', ->
       returnedType = undefined
       el = undefined

@@ -138,7 +138,7 @@
       before ->
         returnedType = minim.convertFromDom el

-      it 'returns array element', ->
+      it 'returns object element', ->
         expect(returnedType.elementType()).to.equal 'object'

       it 'has the correct values', ->


change thresholds

change thresholds

--- src/player.coffee
+++ src/player.coffee
@@ -42,7 +42,7 @@

     if value >= 0.5
       bet = if gameState.players[0]?.status == 'active'
-        800
+        500
       else
         300
       if value < 1


removed console.logs

removed console.logs

--- src/player.coffee
+++ src/player.coffee
@@ -56,7 +56,6 @@
       $('#controls').append '

'
       youtubeVideo song.YTId, videoPlayerOptions, (err, player) ->
         return console.log err if err
-        console.log "ALMA", player.getVideoData()
         $scope.videoLength = player.getDuration()
         durationInterval = setInterval ->
           $scope.timeElapsed = videoPlayer.getCurrentTime()
@@ -100,6 +99,4 @@

   player.controller 'addController', require './add'

-  console.log "ALMA", require('./confirm').controller
-
   player.controller 'confirmController', require('./confirm').controller


fixed lists lexer

fixed lists lexer

--- src/poster.coffee
+++ src/poster.coffee
@@ -66,60 +66,60 @@

         @tokens.push type: 'blockquote_end'
       # list
-      # else if cap = @rules.list.exec src
-      #   src = src[cap[0].length..]
-      #   bull = cap[2]
-
-      #   @tokens.push
-      #     type: 'list_start'
-      #     ordered: bull.length > 1
-
-      #   # Get each top-level item.
-      #   cap = cap[0].match @rules.item
-
-      #   next = false
-      #   l = cap.length
-      #   i = 0
-
-      #   while i < l
-      #     i++
-      #     item = cap[i]
-
-      #     # Remove the list item's bullet
-      #     # so it is seen as the next token.
-      #     space = item.length
-      #     item = item.replace /^ *([*+-]|\d+\.) +/, ''
-
-      #     # Outdent whatever the
-      #     # list item contains. Hacky.
-      #     if ~item.indexOf '\n '
-      #       space -= item.length
-      #       item = if !@options.pedantic then item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') else item.replace(/^ {1,4}/gm, '')
-
-      #     # Determine whether the next list item belongs here.
-      #     # Backpedal if it does not belong in this list.
-      #     if @options.smartLists and i != l - 1
-      #       b = block.bullet.exec(cap[i+1])[0]
-      #       if bull != b and !(bull.length > 1 and b.length > 1)
-      #         src = cap.slice(i + 1).join('\n') + src
-      #         i = l - 1
-
-      #     # Determine whether item is loose or not.
-      #     # Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
-      #     # for discount behavior.
-      #     loose = next || /\n\n(?!\s*$)/.test(item)
-      #     if i != l - 1
-      #       next = (item[item.length-1] == '\n')
-      #       loose = next if not loose
-
-      #     @tokens.push type: if loose then 'loose_item_start' else 'list_item_start'
-
-      #     # Recurse.
-      #     @token item, false
-
-      #     @tokens.push type: 'list_item_end'
-      #   @tokens.push
-      #     type: 'list_end'
+      else if cap = @rules.list.exec src
+        src = src[cap[0].length..]
+        bull = cap[2]
+
+        @tokens.push
+          type: 'list_start'
+          ordered: bull.length > 1
+
+        # Get each top-level item.
+        cap = cap[0].match @rules.item
+
+        next = false
+        l = cap.length
+        i = 0
+
+        while i < l
+          item = cap[i]
+
+          # Remove the list item's bullet
+          # so it is seen as the next token.
+          space = item.length
+          item = item.replace /^ *([*+-]|\d+\.) +/, ''
+
+          # Outdent whatever the
+          # list item contains. Hacky.
+          if ~item.indexOf '\n '
+            space -= item.length
+            item = if not @options.pedantic then item.replace(new RegExp("^ {1,#{space}}", 'gm'), '') else item.replace(/^ {1,4}/gm, '')
+
+          # Determine whether the next list item belongs here.
+          # Backpedal if it does not belong in this list.
+          if @options.smartLists and i != l - 1
+            b = /(?:[*+-]|\d+\.)/.exec(cap[i+1])[0]
+            if bull != b and not (bull.length > 1 and b.length > 1)
+              src = cap.slice(i + 1).join('\n') + src
+              i = l - 1
+
+          # Determine whether item is loose or not.
+          # Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
+          # for discount behavior.
+          loose = next || /\n\n(?!\s*$)/.test(item)
+          if i != l - 1
+            next = (item[item.length-1] == '\n')
+            loose = next if not loose
+
+          @tokens.push type: if loose then 'loose_item_start' else 'list_item_start'
+
+          # Recurse.
+          @token item, false
+
+          @tokens.push type: 'list_item_end'
+
+          i++
+        @tokens.push type: 'list_end'
       # html
       else if cap = @rules.html.exec src
         src = src[cap[0].length..]


Set cross-domain allowed to default to '*'

--- src/postie.coffee
+++ src/postie.coffee
@@ -1,5 +1,6 @@
 class Postie
   listenCallback: null
+  domain: '*'

   constructor: (options = {}) ->


:lipstick: Clear object deconstruction in Selection::copy

--- src/selection.coffee
+++ src/selection.coffee
@@ -512,10 +512,9 @@
     return if @isEmpty()
     text = @editor.buffer.getTextInRange(@getBufferRange())
     if maintainClipboard
-      {text: clipboardText, metadata: clipboardMetadata} = atom.clipboard.readWithMetadata()
+      {text: clipboardText, metadata} = atom.clipboard.readWithMetadata()

       if clipboardMetadata? and clipboardMetadata.selections?
-        metadata = clipboardMetadata
         clipboardMetadata.selections.push(text)
       else
         metadata = { selections: [clipboardText, text] }


Bind jQuery events.

Bind jQuery events.

--- src/puzzle.coffee
+++ src/puzzle.coffee
@@ -141,6 +141,7 @@
         @bindings = {
             one: {}
             always: {}
+            proxy: {}
         }

         @div = $('

')
@@ -189,13 +190,13 @@

         return this

-    # bind event 'puzzle.done' and 'puzzle.step' to callback.
+    # bind event 'done' and 'step' to callback.
     stepCallback = (callback) ->
         =>
             callback?.apply(this)
-            @trigger('puzzle.step')
+            @trigger('step')
             if @board.isComplete() and @board.status.moving == 0
-                @board.trigger('puzzle.done')
+                @board.trigger('done')

     # shift current square if it's adjacent empty square.
     step: (callback) ->
@@ -227,6 +228,13 @@
         if event not of bindings
             bindings[event] = []
         bindings[event].push(handler)
+
+        if event not in ['step']
+            @bindings.proxy[handler] = => handler.apply(this)
+            if one
+                @div.one(event, @bindings.proxy[handler])
+            else
+                @div.bind(event, @bindings.proxy[handler])

         return this

@@ -253,6 +261,14 @@
                     (cc for cc in bindings[event] when cc isnt handler)
                 else
                     []
+
+        if event not in ['step']
+            if handler of @bindings.proxy
+                @div.off(event, @bindings.proxy[handler])
+                delete @bindings.proxy[handler]
+            else
+                @div.off(event, handler)
+
         return this

     one: (event, handler) ->
@@ -317,16 +333,16 @@
     redraw = ->
         # redraw after shuffling, resetting, or stepping.
         if @status.shuffling > 0
-            @one('puzzle.shuffle', redraw)
+            @one('shuffle', redraw)

         else if @status.resetting > 0
-            @one('puzzle.reset', redraw)
-
-        else if @status.moving > 0
+            @one('reset', redraw)
+
+        else if @status.moving > 0 #todo
             handler = =>
                 redraw.apply(this)
-                @each(-> @unbind('puzzle.step', handler))
-            @each(-> @one('puzzle.step', handler))
+                @each(-> @unbind('step', handler))
+            @each(-> @one('step', handler))

         recalc.apply(this)

@@ -421,7 +437,7 @@

     shuffle: (callback) ->
         if @status.shuffling > 0
-            return @one('puzzle.shuffle', Puzzle.prototype.shuffle)
+            return @one('shuffle', Puzzle.prototype.shuffle)

         # place empty square to the bottom right corner
         if @squareMatrix[@rows][@cols]
@@ -457,7 +473,7 @@
         once = runOnceAt(@squareList.length, =>
             @status.shuffling -= 1
             callback?.apply(this)
-            @trigger('puzzle.shuffle')
+            @trigger('shuffle')
         )

         @status.shuffling += 1
@@ -490,9 +506,13 @@
     # bind jQuery events or specific puzzle events
     bind: (event, handler, one=false) ->
         bindings = if one then @bindings.one else @bindings.always
+
         if event not of bindings
             bindings[event] = []
         bindings[event].push(handler)
+
+        if event not in ['shuffle', 'reset', 'done']
+            @each(-> @bind(event, handler, one))

         return this

@@ -505,6 +525,10 @@
                     (cc for cc in bindings[event] when cc isnt handler)
                 else
                     []
+
+        if event not in ['shuffle', 'reset', 'done']
+            @each(-> @unbind(event, handler))
+
         return this

     # handler runs at most one time.
@@ -531,7 +555,7 @@
         once = runOnceAt(@squareList.length, =>
             @status.resetting -= 1
             callback?.apply(this)
-            @trigger('puzzle.reset'))
+            @trigger('reset'))
         swap.call(sq, sq.origRow, sq.origCol) for sq in @squareList
         @status.resetting += 1
         @each(-> slowlyMove.call(this, @row, @col, once))


Implement an eventHandler factory on Rivets.Binding.

--- src/rivets.coffee
+++ src/rivets.coffee
@@ -56,6 +56,11 @@
         value = formatter value, args...

     value
+
+  # Returns an event handler for the binding around the supplied function.
+  eventHandler: (fn) =>
+    handler = (binding = @).view.config.handler
+    (ev) -> handler.call fn, @, ev, binding

   # Sets the value for the binding. This Basically just runs the binding routine
   # with the suplied value formatted.


consolidate upper/lower resize handlers

consolidate upper/lower resize handlers

--- app/assets/javascripts/components/mark/tools/text-row-tool/index.cjsx
+++ app/assets/javascripts/components/mark/tools/text-row-tool/index.cjsx
@@ -29,11 +29,8 @@
     console.log 'lkxjdhklsjdh'
     @update @props.getEventOffset(e)

-  handleUpperResize: ->
-    console.log 'handleUpperResize()'
-
-  handleLowerResize: ->
-    console.log 'handleLowerResize()'
+  handleResize: (whichOne, e) ->
+    console.log 'handleUpperResize() ', whichOne

   update: ({x,y}) ->
     mark = @state.mark
@@ -75,7 +72,7 @@
             <ResizeButton
               viewBox={"0 0 @props.imageWidth @props.imageHeight"}
               className="upperResize"
-              handleResize={@handleUpperResize}
+              handleResize={@handleResize.bind null, 'upper'}
               transform={"translate( #{@props.imageWidth/2}, #{ - Math.round scrubberHeight/2 } )"}
               scrubberHeight={scrubberHeight}
               scrubberWidth={scrubberWidth}
@@ -84,7 +81,7 @@

             <ResizeButton
               className="lowerResize"
-              handleResize={@handleLowerResize}
+              handleResize={@handleResize.bind null, 'lower'}
               transform={"translate( #{@props.imageWidth/2}, #{ Math.round( markHeight - scrubberHeight/2 ) } )"}
               scrubberHeight={scrubberHeight}
               scrubberWidth={scrubberWidth}


Revise hashbang snippet added in d181298

* Prefix changed to "apl". Typing this is somewhat easier than "#".
* Hashbang path changed to use /usr/bin/env, which is more portable.
* Tab-stops removed; users won't likely need to change the hashbang path
  or arguments. Having tab-stops also makes it impossible to indent code
  after insertion, as the cursor jumps back to the hashbang directive.
* An ")OFF" command has been included, necessary to terminate GNU APL.

--- snippets/language-apl.cson
+++ snippets/language-apl.cson
@@ -13,8 +13,8 @@
 		body:   "${1:⎕ }← ${2:VALUE}"

 	Hashbang:
-		prefix: "#"
-		body: "#!/usr/${3:local/bin/apl} ${2:--noCIN -f}$1"
+		prefix: "apl"
+		body: "#!/usr/bin/env apl --script\n\n$1\n\n)OFF"

 	# GNU APL extensions


updated comments

updated comments

--- src/router.coffee
+++ src/router.coffee
@@ -52,7 +52,7 @@
         ###
         Render views. Fetch templates and resolve objects then bind that on views.
         @param locationChanged {bool} If location was changed, it should be yes.
-        @param reload {bool} If it is true, it will re-render all views.
+        @param reload {bool|string} If it is true, it will re-render all views. string: reload the namespace.
         @param nextRule {rule} direct select the next rule.
         ###
         diffRuleIndex = 0


Thicker stickers for older graphics cards (?)

--- source/Pieces3D.js.coffee
+++ source/Pieces3D.js.coffee
@@ -3,7 +3,7 @@

 # Pieces3D.UFR, Pieces3D.DL, Pieces3D.B etc are the 3D models for those pieces
 class @Pieces3D
-  TINY = 0.0005
+  TINY = 0.0030

   constructor: (scene, config, @use_canvas) ->
     @at = {}


Use snippets parser in snippets spec

Use snippets parser in snippets spec

--- spec/snippets-spec.coffee
+++ spec/snippets-spec.coffee
@@ -10,16 +10,18 @@
     atom.keymaps.handleKeyboardEvent(event)

   sanitize = (body) ->
-    body =
-      body
-      .replace /\\\\/g, '\\'
+    parser = Snippets.getBodyParser()
+    flatten = (obj) ->
+      if typeof(obj) is "string"
+        return obj
+      else
+        return obj.content.map(flatten).join('')
+    parsed =
+      parser.parse(body)
+      .map(flatten)
+      .join('')
       .replace /\t/g, ' '.repeat(editor.getTabLength())
-    replace = (b) ->
-      b.replace /\$\d|\$\{\d\}|\$\{\d:([^}]*)\}/g, (match, p1) -> p1 or ""
-    ob = body
-    while (nb = replace(ob)) isnt ob
-      ob = nb
-    return ob
+    return parsed

   universalTests = ->
     it 'triggers snippets', ->


Consistent capitalisation

Consistent capitalisation

--- test/unit/model/user.coffee
+++ test/unit/model/user.coffee
@@ -52,7 +52,7 @@
       @user.set @attrs, validate: true
       @eventSpy.calledOnce.should.be.true

-describe 'User (Server)', ->
+describe 'User (server)', ->
   before ->
     mongoose.connect process.env.CU_DB

@@ -194,3 +194,4 @@
       # TODO: stub nodemailer
       xit 'emails the user', ->
         @emailStub.calledOnce.should.be.true
+


syntax conv refactorings in the remote module

--- library/membrane/remote.coffee
+++ library/membrane/remote.coffee
@@ -51,7 +51,7 @@
     return f() unless _.isObject @compiled
     hierarchy = try @compiled.hierarchy?()
     assert _.isArray(hierarchy), "no hierarchy"
-    hasRemote = (x) ->_.isObject x.remote
+    hasRemote = (x) -> _.isObject x.remote
     compilation = (x) -> x.remote.compile caching
     areRemote = _.filter hierarchy, hasRemote
     compiled = _.map areRemote, compilation
@@ -67,17 +67,17 @@
 remote = module.exports.remote = (wrapper) ->
     noWrapper = "wrapper must be a function"
     invalidArgs = "wrapper cannot have arguments"
-    assert _.isFunction wrapper, noWrapper
+    assert _.isFunction(wrapper), noWrapper
     assert wrapper.length is 0, invalidArgs
     try compiled = wrapper() catch error
         msg = "compilation failed: #{error.message}"
-        error.message = msg; throw error
-    compiled.remote = Object.create {}
-    compiled.remote.compiled = compiled
+        error.message = msg.toString(); throw error
+    assert compiled.remote = Object.create {}
+    assert compiled.remote.compiled = compiled
+    assert compiled.remote.compile = compiler
+    assert compiled.remote.source = wrapper
     compiled.remote.symbol = compiled.name
-    compiled.remote.source = wrapper.toString()
-    compiled.remote.compile = compiler
-    return compiled
+    assert _.isFunction compiled; compiled

 # A handy shortcut for the `remote` decorator that can be used with
 # pure functions (not classes) to avoid having the top level, zero
@@ -90,9 +90,9 @@
     assert.ok _.isFunction(compiled), notFunction
     assert not compiled.__super__?, wrongCompiled
     wrapper = "function() { return #{compiled} }"
-    compiled.remote = Object.create {}
-    compiled.remote.compiled = compiled
+    assert compiled.remote = Object.create {}
+    assert compiled.remote.compiled = compiled
+    assert compiled.remote.compile = compiler
+    assert compiled.remote.source = wrapper
     compiled.remote.symbol = compiled.name
-    compiled.remote.source = wrapper.toString()
-    compiled.remote.compile = compiler
-    return compiled
+    assert _.isFunction compiled; compiled


minors again

minors again

--- library/membrane/visual.coffee
+++ library/membrane/visual.coffee
@@ -112,7 +112,7 @@
     # JavaScript sources passed in as the remote objects. Please refer
     # to the implementation and the class for more information on it.
     inject: (context, subject, symbol) ->
-        caching = context.caching ?= new Object
+        assert caching = context.caching ?= new Object
         scripts = -> assert context.scripts.push subject
         sources = -> assert context.sources.push compile()
         compile = -> subject.remote.compile caching, symbol


add theme & package search filter

add theme & package search filter

--- src/search.coffee
+++ src/search.coffee
@@ -20,12 +20,21 @@
     """
     options.alias('h', 'help').describe('help', 'Print this usage message')
     options.boolean('json').describe('json', 'Output matching packages as JSON array')
+    options.boolean('packages').describe('packages', 'Search only non-theme packages').alias('p', 'packages')
+    options.boolean('themes').describe('themes', 'Search only themes').alias('t', 'themes')

-  searchPackages: (query, callback) ->
+  searchPackages: (query, opts, callback) ->
+    qs =
+      q: query
+
+    if opts.packages
+      qs.filter = 'package'
+    else if opts.themes
+      qs.filter = 'theme'
+
     requestSettings =
       url: "#{config.getAtomPackagesUrl()}/search"
-      qs:
-        q: query
+      qs: qs
       json: true
       proxy: process.env.http_proxy || process.env.https_proxy

@@ -50,7 +59,11 @@
       callback("Missing required search query")
       return

-    @searchPackages query, (error, packages) ->
+    searchOptions =
+      packages: options.argv.packages
+      themes: options.argv.themes
+
+    @searchPackages query, searchOptions, (error, packages) ->
       if error?
         callback(error)
         return


error handling

error handling

--- src/server.coffee
+++ src/server.coffee
@@ -59,11 +59,18 @@
       if url.match(pattern)
         return res.status(400).send("Invalid URL")

-    request url, (err, _res, src) ->
+    request url, (err, urlRes, src) ->
       if err?
+        if err.code == 'ENOTFOUND'
+          return res.status(400).send("Failed to retrieve URL")
+
         console.error err
         res.sendStatus(500)
+
       else
+        if urlRes.statusCode != 200
+          return res.status(400).send("Failed to retrieve URL")
+
         render src, (err, png) ->
           if err?
             console.error err


Fixing console launch on OSX

Fixing console launch on OSX

--- src/server.coffee
+++ src/server.coffee
@@ -128,6 +128,6 @@
     msg.error message
     throw message
   else
-    host = if addr.address is '0.0.0.0' then 'localhost' else addr.address
+    host = if addr.address in ['0.0.0.0', '::'] then 'localhost' else addr.address
     "http://#{host}:#{addr.port}/$console"


Caught UPD server stop error

Caught UPD server stop error

--- src/server.coffee
+++ src/server.coffee
@@ -494,8 +494,11 @@
     promises.push stopServer(auditTcpServer, 'Audit TCP').promise if auditTcpServer

     if auditUDPServer
-      auditUDPServer.close()
-      logger.info "Stopped Audit UDP server"
+      try
+        auditUDPServer.close()
+        logger.info "Stopped Audit UDP server"
+      catch err
+        # ignore errors when shutting down the server, sometimes its already stopped

     if tcpHttpReceiver
       promises.push stopServer(tcpHttpReceiver, 'TCP HTTP Receiver')


User Camel-Case Naming

User Camel-Case Naming

--- modules/processor.coffee
+++ modules/processor.coffee
@@ -3,14 +3,14 @@
 fs = require('fs')
 path = require('path')

-bRecordAPI = false
+isRecordAPIOpen = false

 exports.processData = (req, data) ->
   ui.turnOn()
   data = data.toString()
   ############################
   # Record API Information
-  if (bRecordAPI)
+  if (isRecordAPIOpen)
     filePath = url.parse(req.url).pathname.substr 1
     fileDir = path.dirname filePath
     fs.mkdir fileDir, (err) ->


Pass in empty strings to null any values that might not be present (postal code, for instance)

--- models/current_user.coffee
+++ models/current_user.coffee
@@ -151,9 +151,8 @@

   setGeo: (geo) ->
     @set location:
-      city        : geo.getCity()
-      state       : geo.getState()
-      state_code  : geo.getStateCode()
-      postal_code : geo.getPostalCode()
-      country     : geo.getCountry()
-      coordinates : geo.getCoordinates()
+      city        : geo.getCity() or ''
+      state       : geo.getState() or ''
+      state_code  : geo.getStateCode() or ''
+      postal_code : geo.getPostalCode() or ''
+      coordinates : geo.getCoordinates() or ''


More tests for multiple fields with mixed updates.

--- tests.coffee
+++ tests.coffee
@@ -3190,6 +3190,9 @@
     Post.documents.insert
       author:
         _id: @person1._id
+        # To test what happens if fields are partially not up to date
+        username: 'wrong'
+        displayName: 'wrong'
       subscribers: [
         _id: @person2._id
       ,
@@ -3209,10 +3212,20 @@
           _id: @person2._id
         persons: [
           _id: @person2._id
-        ,
-          _id: @person2._id
-        ,
-          _id: @person3._id
+          username: 'wrong'
+          displayName: 'wrong'
+        ,
+          _id: @person2._id
+          username: @person2.username
+          displayName: @person2.displayName
+          field1: 'wrong'
+        ,
+          _id: @person3._id
+          # To test if the second person3 value will be updated
+          username: @person3.username
+          displayName: @person3.displayName
+          field1: @person3.field1
+          field2: @person3.field2
         ,
           _id: @person3._id
         ]
@@ -3220,30 +3233,37 @@
       nested: [
         required:
           _id: @person2._id
-        optional:
-          _id: @person3._id
-        body: 'NestedFooBar'
-      ,
-        required:
-          _id: @person2._id
-        optional:
-          _id: @person3._id
-        body: 'NestedFooBar'
-      ,
-        required:
-          _id: @person3._id
-        optional:
-          _id: @person2._id
-        body: 'NestedFooBar'
-      ,
-        required:
-          _id: @person3._id
-        optional:
-          _id: @person2._id
-        body: 'NestedFooBar'
-      ,
-        required:
-          _id: @person2._id
+          username: 'wrong'
+          displayName: 'wrong'
+        optional:
+          _id: @person3._id
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person2._id
+          username: @person2.username
+          displayName: @person2.displayName
+        optional:
+          _id: @person3._id
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+        optional:
+          _id: @person2._id
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+        optional:
+          _id: @person2._id
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person2._id
+          username: @person2.username
+          displayName: @person2.displayName
+          field1: 'wrong'
         optional:
           _id: @person2._id
         body: 'NestedFooBar'
@@ -3616,9 +3636,12 @@
         'tag-6-prefix-foobar-nestedfoobar-suffix'
       ]

+  (test, expect) ->
     Person.documents.update @person1Id,
-      $unset:
-        username: ''
+      $set:
+        # Updating two fields at the same time
+        field1: 'Field 1 - 1a'
+        field2: 'Field 1 - 2a'
     ,
       expect (error, res) =>
         test.isFalse error, error?.toString?() or error
@@ -3631,10 +3654,10 @@
     @person1 = Person.documents.findOne @person1Id

     test.instanceOf @person1, Person
-    test.isUndefined @person1.username, @person1.username
+    test.equal @person1.username, 'person1a'
     test.equal @person1.displayName, 'Person 1'
-    test.equal @person1.field1, 'Field 1 - 1'
-    test.equal @person1.field2, 'Field 1 - 2'
+    test.equal @person1.field1, 'Field 1 - 1a'
+    test.equal @person1.field2, 'Field 1 - 2a'

     @post = Post.documents.findOne @postId,
       transform: null # So that we can use test.equal
@@ -3644,6 +3667,7 @@
       _schema: '1.0.0'
       author:
         _id: @person1._id
+        username: @person1.username
         displayName: @person1.displayName
         field1: @person1.field1
         field2: @person1.field2
@@ -3783,7 +3807,7 @@
         'tag-6-prefix-foobar-nestedfoobar-suffix'
       ]

-    Person.documents.update @person2Id,
+    Person.documents.update @person1Id,
       $unset:
         username: ''
     ,
@@ -3795,13 +3819,13 @@
     Meteor.setTimeout expect(), WAIT_TIME
 ,
   (test, expect) ->
-    @person2 = Person.documents.findOne @person2Id
-
-    test.instanceOf @person2, Person
-    test.isUndefined @person2.username, @person2.username
-    test.equal @person2.displayName, 'Person 2'
-    test.equal @person2.field1, 'Field 2 - 1'
-    test.equal @person2.field2, 'Field 2 - 2'
+    @person1 = Person.documents.findOne @person1Id
+
+    test.instanceOf @person1, Person
+    test.isUndefined @person1.username, @person1.username
+    test.equal @person1.displayName, 'Person 1'
+    test.equal @person1.field1, 'Field 1 - 1a'
+    test.equal @person1.field2, 'Field 1 - 2a'

     @post = Post.documents.findOne @postId,
       transform: null # So that we can use test.equal
@@ -3823,6 +3847,7 @@
       ]
       reviewers: [
         _id: @person2._id
+        username: @person2.username
       ,
         _id: @person3._id
         username: @person3.username
@@ -3833,16 +3858,19 @@
       subdocument:
         person:
           _id: @person2._id
+          username: @person2.username
           displayName: @person2.displayName
           field1: @person2.field1
           field2: @person2.field2
         persons: [
           _id: @person2._id
-          displayName: @person2.displayName
-          field1: @person2.field1
-          field2: @person2.field2
-        ,
-          _id: @person2._id
+          username: @person2.username
+          displayName: @person2.displayName
+          field1: @person2.field1
+          field2: @person2.field2
+        ,
+          _id: @person2._id
+          username: @person2.username
           displayName: @person2.displayName
           field1: @person2.field1
           field2: @person2.field2
@@ -3864,55 +3892,61 @@
       nested: [
         required:
           _id: @person2._id
-          displayName: @person2.displayName
-          field1: @person2.field1
-          field2: @person2.field2
-        optional:
-          _id: @person3._id
-          username: @person3.username
-        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
-        body: 'NestedFooBar'
-      ,
-        required:
-          _id: @person2._id
-          displayName: @person2.displayName
-          field1: @person2.field1
-          field2: @person2.field2
-        optional:
-          _id: @person3._id
-          username: @person3.username
-        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
-        body: 'NestedFooBar'
-      ,
-        required:
-          _id: @person3._id
-          username: @person3.username
-          displayName: @person3.displayName
-          field1: @person3.field1
-          field2: @person3.field2
-        optional:
-          _id: @person2._id
-        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
-        body: 'NestedFooBar'
-      ,
-        required:
-          _id: @person3._id
-          username: @person3.username
-          displayName: @person3.displayName
-          field1: @person3.field1
-          field2: @person3.field2
-        optional:
-          _id: @person2._id
-        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
-        body: 'NestedFooBar'
-      ,
-        required:
-          _id: @person2._id
-          displayName: @person2.displayName
-          field1: @person2.field1
-          field2: @person2.field2
-        optional:
-          _id: @person2._id
+          username: @person2.username
+          displayName: @person2.displayName
+          field1: @person2.field1
+          field2: @person2.field2
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person2._id
+          username: @person2.username
+          displayName: @person2.displayName
+          field1: @person2.field1
+          field2: @person2.field2
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+          displayName: @person3.displayName
+          field1: @person3.field1
+          field2: @person3.field2
+        optional:
+          _id: @person2._id
+          username: @person2.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+          displayName: @person3.displayName
+          field1: @person3.field1
+          field2: @person3.field2
+        optional:
+          _id: @person2._id
+          username: @person2.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person2._id
+          username: @person2.username
+          displayName: @person2.displayName
+          field1: @person2.field1
+          field2: @person2.field2
+        optional:
+          _id: @person2._id
+          username: @person2.username
         slug: 'nested-prefix-foobar-nestedfoobar-suffix'
         body: 'NestedFooBar'
       ,
@@ -3940,7 +3974,7 @@
         'tag-6-prefix-foobar-nestedfoobar-suffix'
       ]

-    Person.documents.update @person3Id,
+    Person.documents.update @person2Id,
       $unset:
         username: ''
     ,
@@ -3952,13 +3986,13 @@
     Meteor.setTimeout expect(), WAIT_TIME
 ,
   (test, expect) ->
-    @person3 = Person.documents.findOne @person3Id
-
-    test.instanceOf @person3, Person
-    test.isUndefined @person3.username, @person3.username
-    test.equal @person3.displayName, 'Person 3'
-    test.equal @person3.field1, 'Field 3 - 1'
-    test.equal @person3.field2, 'Field 3 - 2'
+    @person2 = Person.documents.findOne @person2Id
+
+    test.instanceOf @person2, Person
+    test.isUndefined @person2.username, @person2.username
+    test.equal @person2.displayName, 'Person 2'
+    test.equal @person2.field1, 'Field 2 - 1'
+    test.equal @person2.field2, 'Field 2 - 2'

     @post = Post.documents.findOne @postId,
       transform: null # So that we can use test.equal
@@ -3982,8 +4016,10 @@
         _id: @person2._id
       ,
         _id: @person3._id
-      ,
-        _id: @person3._id
+        username: @person3.username
+      ,
+        _id: @person3._id
+        username: @person3.username
       ]
       subdocument:
         person:
@@ -4003,11 +4039,13 @@
           field2: @person2.field2
         ,
           _id: @person3._id
-          displayName: @person3.displayName
-          field1: @person3.field1
-          field2: @person3.field2
-        ,
-          _id: @person3._id
+          username: @person3.username
+          displayName: @person3.displayName
+          field1: @person3.field1
+          field2: @person3.field2
+        ,
+          _id: @person3._id
+          username: @person3.username
           displayName: @person3.displayName
           field1: @person3.field1
           field2: @person3.field2
@@ -4022,56 +4060,62 @@
           field2: @person2.field2
         optional:
           _id: @person3._id
-        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
-        body: 'NestedFooBar'
-      ,
-        required:
-          _id: @person2._id
-          displayName: @person2.displayName
-          field1: @person2.field1
-          field2: @person2.field2
-        optional:
-          _id: @person3._id
-        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
-        body: 'NestedFooBar'
-      ,
-        required:
-          _id: @person3._id
-          displayName: @person3.displayName
-          field1: @person3.field1
-          field2: @person3.field2
-        optional:
-          _id: @person2._id
-        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
-        body: 'NestedFooBar'
-      ,
-        required:
-          _id: @person3._id
-          displayName: @person3.displayName
-          field1: @person3.field1
-          field2: @person3.field2
-        optional:
-          _id: @person2._id
-        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
-        body: 'NestedFooBar'
-      ,
-        required:
-          _id: @person2._id
-          displayName: @person2.displayName
-          field1: @person2.field1
-          field2: @person2.field2
-        optional:
-          _id: @person2._id
-        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
-        body: 'NestedFooBar'
-      ,
-        required:
-          _id: @person3._id
-          displayName: @person3.displayName
-          field1: @person3.field1
-          field2: @person3.field2
-        optional:
-          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person2._id
+          displayName: @person2.displayName
+          field1: @person2.field1
+          field2: @person2.field2
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+          displayName: @person3.displayName
+          field1: @person3.field1
+          field2: @person3.field2
+        optional:
+          _id: @person2._id
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+          displayName: @person3.displayName
+          field1: @person3.field1
+          field2: @person3.field2
+        optional:
+          _id: @person2._id
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person2._id
+          displayName: @person2.displayName
+          field1: @person2.field1
+          field2: @person2.field2
+        optional:
+          _id: @person2._id
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+          displayName: @person3.displayName
+          field1: @person3.field1
+          field2: @person3.field2
+        optional:
+          _id: @person3._id
+          username: @person3.username
         slug: 'nested-prefix-foobar-nestedfoobar-suffix'
         body: 'NestedFooBar'
       ]
@@ -4087,6 +4131,153 @@
         'tag-6-prefix-foobar-nestedfoobar-suffix'
       ]

+    Person.documents.update @person3Id,
+      $unset:
+        username: ''
+    ,
+      expect (error, res) =>
+        test.isFalse error, error?.toString?() or error
+        test.isTrue res
+
+    # Sleep so that observers have time to update documents
+    Meteor.setTimeout expect(), WAIT_TIME
+,
+  (test, expect) ->
+    @person3 = Person.documents.findOne @person3Id
+
+    test.instanceOf @person3, Person
+    test.isUndefined @person3.username, @person3.username
+    test.equal @person3.displayName, 'Person 3'
+    test.equal @person3.field1, 'Field 3 - 1'
+    test.equal @person3.field2, 'Field 3 - 2'
+
+    @post = Post.documents.findOne @postId,
+      transform: null # So that we can use test.equal
+
+    test.equal @post,
+      _id: @postId
+      _schema: '1.0.0'
+      author:
+        _id: @person1._id
+        displayName: @person1.displayName
+        field1: @person1.field1
+        field2: @person1.field2
+      subscribers: [
+        _id: @person2._id
+      ,
+        _id: @person2._id
+      ,
+        _id: @person3._id
+      ]
+      reviewers: [
+        _id: @person2._id
+      ,
+        _id: @person3._id
+      ,
+        _id: @person3._id
+      ]
+      subdocument:
+        person:
+          _id: @person2._id
+          displayName: @person2.displayName
+          field1: @person2.field1
+          field2: @person2.field2
+        persons: [
+          _id: @person2._id
+          displayName: @person2.displayName
+          field1: @person2.field1
+          field2: @person2.field2
+        ,
+          _id: @person2._id
+          displayName: @person2.displayName
+          field1: @person2.field1
+          field2: @person2.field2
+        ,
+          _id: @person3._id
+          displayName: @person3.displayName
+          field1: @person3.field1
+          field2: @person3.field2
+        ,
+          _id: @person3._id
+          displayName: @person3.displayName
+          field1: @person3.field1
+          field2: @person3.field2
+        ]
+        slug: 'subdocument-prefix-foobar-subdocumentfoobar-suffix'
+        body: 'SubdocumentFooBar'
+      nested: [
+        required:
+          _id: @person2._id
+          displayName: @person2.displayName
+          field1: @person2.field1
+          field2: @person2.field2
+        optional:
+          _id: @person3._id
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person2._id
+          displayName: @person2.displayName
+          field1: @person2.field1
+          field2: @person2.field2
+        optional:
+          _id: @person3._id
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+          displayName: @person3.displayName
+          field1: @person3.field1
+          field2: @person3.field2
+        optional:
+          _id: @person2._id
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+          displayName: @person3.displayName
+          field1: @person3.field1
+          field2: @person3.field2
+        optional:
+          _id: @person2._id
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person2._id
+          displayName: @person2.displayName
+          field1: @person2.field1
+          field2: @person2.field2
+        optional:
+          _id: @person2._id
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+          displayName: @person3.displayName
+          field1: @person3.field1
+          field2: @person3.field2
+        optional:
+          _id: @person3._id
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ]
+      body: 'FooBar'
+      slug: 'prefix-foobar-subdocumentfoobar-suffix'
+      tags: [
+        'tag-0-prefix-foobar-subdocumentfoobar-suffix'
+        'tag-1-prefix-foobar-nestedfoobar-suffix'
+        'tag-2-prefix-foobar-nestedfoobar-suffix'
+        'tag-3-prefix-foobar-nestedfoobar-suffix'
+        'tag-4-prefix-foobar-nestedfoobar-suffix'
+        'tag-5-prefix-foobar-nestedfoobar-suffix'
+        'tag-6-prefix-foobar-nestedfoobar-suffix'
+      ]
+
     Person.documents.update @person1Id,
       $set:
         username: 'person1b'
@@ -4104,8 +4295,8 @@
     test.instanceOf @person1, Person
     test.equal @person1.username, 'person1b'
     test.equal @person1.displayName, 'Person 1'
-    test.equal @person1.field1, 'Field 1 - 1'
-    test.equal @person1.field2, 'Field 1 - 2'
+    test.equal @person1.field1, 'Field 1 - 1a'
+    test.equal @person1.field2, 'Field 1 - 2a'

     @post = Post.documents.findOne @postId,
       transform: null # So that we can use test.equal
@@ -4412,6 +4603,336 @@
     test.equal @person3.displayName, 'Person 3'
     test.equal @person3.field1, 'Field 3 - 1'
     test.equal @person3.field2, 'Field 3 - 2'
+
+    @post = Post.documents.findOne @postId,
+      transform: null # So that we can use test.equal
+
+    test.equal @post,
+      _id: @postId
+      _schema: '1.0.0'
+      author:
+        _id: @person1._id
+        username: @person1.username
+        displayName: @person1.displayName
+        field1: @person1.field1
+        field2: @person1.field2
+      subscribers: [
+        _id: @person2._id
+      ,
+        _id: @person2._id
+      ,
+        _id: @person3._id
+      ]
+      reviewers: [
+        _id: @person2._id
+        username: @person2.username
+      ,
+        _id: @person3._id
+        username: @person3.username
+      ,
+        _id: @person3._id
+        username: @person3.username
+      ]
+      subdocument:
+        person:
+          _id: @person2._id
+          username: @person2.username
+          displayName: @person2.displayName
+          field1: @person2.field1
+          field2: @person2.field2
+        persons: [
+          _id: @person2._id
+          username: @person2.username
+          displayName: @person2.displayName
+          field1: @person2.field1
+          field2: @person2.field2
+        ,
+          _id: @person2._id
+          username: @person2.username
+          displayName: @person2.displayName
+          field1: @person2.field1
+          field2: @person2.field2
+        ,
+          _id: @person3._id
+          username: @person3.username
+          displayName: @person3.displayName
+          field1: @person3.field1
+          field2: @person3.field2
+        ,
+          _id: @person3._id
+          username: @person3.username
+          displayName: @person3.displayName
+          field1: @person3.field1
+          field2: @person3.field2
+        ]
+        slug: 'subdocument-prefix-foobar-subdocumentfoobar-suffix'
+        body: 'SubdocumentFooBar'
+      nested: [
+        required:
+          _id: @person2._id
+          username: @person2.username
+          displayName: @person2.displayName
+          field1: @person2.field1
+          field2: @person2.field2
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person2._id
+          username: @person2.username
+          displayName: @person2.displayName
+          field1: @person2.field1
+          field2: @person2.field2
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+          displayName: @person3.displayName
+          field1: @person3.field1
+          field2: @person3.field2
+        optional:
+          _id: @person2._id
+          username: @person2.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+          displayName: @person3.displayName
+          field1: @person3.field1
+          field2: @person3.field2
+        optional:
+          _id: @person2._id
+          username: @person2.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person2._id
+          username: @person2.username
+          displayName: @person2.displayName
+          field1: @person2.field1
+          field2: @person2.field2
+        optional:
+          _id: @person2._id
+          username: @person2.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+          displayName: @person3.displayName
+          field1: @person3.field1
+          field2: @person3.field2
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ]
+      body: 'FooBar'
+      slug: 'prefix-foobar-subdocumentfoobar-suffix'
+      tags: [
+        'tag-0-prefix-foobar-subdocumentfoobar-suffix'
+        'tag-1-prefix-foobar-nestedfoobar-suffix'
+        'tag-2-prefix-foobar-nestedfoobar-suffix'
+        'tag-3-prefix-foobar-nestedfoobar-suffix'
+        'tag-4-prefix-foobar-nestedfoobar-suffix'
+        'tag-5-prefix-foobar-nestedfoobar-suffix'
+        'tag-6-prefix-foobar-nestedfoobar-suffix'
+      ]
+
+  (test, expect) ->
+    Person.documents.update @person2Id,
+      $unset:
+        # Removing two fields at the same time
+        field1: ''
+        field2: ''
+    ,
+      expect (error, res) =>
+        test.isFalse error, error?.toString?() or error
+        test.isTrue res
+
+    # Sleep so that observers have time to update documents
+    Meteor.setTimeout expect(), WAIT_TIME
+,
+  (test, expect) ->
+    @person2 = Person.documents.findOne @person2Id
+
+    test.instanceOf @person2, Person
+    test.equal @person2.username, 'person2b'
+    test.equal @person2.displayName, 'Person 2'
+    test.isUndefined @person2.field1
+    test.isUndefined @person2.field2
+
+    @post = Post.documents.findOne @postId,
+      transform: null # So that we can use test.equal
+
+    test.equal @post,
+      _id: @postId
+      _schema: '1.0.0'
+      author:
+        _id: @person1._id
+        username: @person1.username
+        displayName: @person1.displayName
+        field1: @person1.field1
+        field2: @person1.field2
+      subscribers: [
+        _id: @person2._id
+      ,
+        _id: @person2._id
+      ,
+        _id: @person3._id
+      ]
+      reviewers: [
+        _id: @person2._id
+        username: @person2.username
+      ,
+        _id: @person3._id
+        username: @person3.username
+      ,
+        _id: @person3._id
+        username: @person3.username
+      ]
+      subdocument:
+        person:
+          _id: @person2._id
+          username: @person2.username
+          displayName: @person2.displayName
+        persons: [
+          _id: @person2._id
+          username: @person2.username
+          displayName: @person2.displayName
+        ,
+          _id: @person2._id
+          username: @person2.username
+          displayName: @person2.displayName
+        ,
+          _id: @person3._id
+          username: @person3.username
+          displayName: @person3.displayName
+          field1: @person3.field1
+          field2: @person3.field2
+        ,
+          _id: @person3._id
+          username: @person3.username
+          displayName: @person3.displayName
+          field1: @person3.field1
+          field2: @person3.field2
+        ]
+        slug: 'subdocument-prefix-foobar-subdocumentfoobar-suffix'
+        body: 'SubdocumentFooBar'
+      nested: [
+        required:
+          _id: @person2._id
+          username: @person2.username
+          displayName: @person2.displayName
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person2._id
+          username: @person2.username
+          displayName: @person2.displayName
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+          displayName: @person3.displayName
+          field1: @person3.field1
+          field2: @person3.field2
+        optional:
+          _id: @person2._id
+          username: @person2.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+          displayName: @person3.displayName
+          field1: @person3.field1
+          field2: @person3.field2
+        optional:
+          _id: @person2._id
+          username: @person2.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person2._id
+          username: @person2.username
+          displayName: @person2.displayName
+        optional:
+          _id: @person2._id
+          username: @person2.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+          displayName: @person3.displayName
+          field1: @person3.field1
+          field2: @person3.field2
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ]
+      body: 'FooBar'
+      slug: 'prefix-foobar-subdocumentfoobar-suffix'
+      tags: [
+        'tag-0-prefix-foobar-subdocumentfoobar-suffix'
+        'tag-1-prefix-foobar-nestedfoobar-suffix'
+        'tag-2-prefix-foobar-nestedfoobar-suffix'
+        'tag-3-prefix-foobar-nestedfoobar-suffix'
+        'tag-4-prefix-foobar-nestedfoobar-suffix'
+        'tag-5-prefix-foobar-nestedfoobar-suffix'
+        'tag-6-prefix-foobar-nestedfoobar-suffix'
+      ]
+
+  (test, expect) ->
+    Person.documents.update @person2Id,
+      $set:
+        # Restoring two fields at the same time
+        field1: 'Field 2 - 1'
+        field2: 'Field 2 - 2'
+    ,
+      expect (error, res) =>
+        test.isFalse error, error?.toString?() or error
+        test.isTrue res
+
+    # Sleep so that observers have time to update documents
+    Meteor.setTimeout expect(), WAIT_TIME
+,
+  (test, expect) ->
+    @person2 = Person.documents.findOne @person2Id
+
+    test.instanceOf @person2, Person
+    test.equal @person2.username, 'person2b'
+    test.equal @person2.displayName, 'Person 2'
+    test.equal @person2.field1, 'Field 2 - 1'
+    test.equal @person2.field2, 'Field 2 - 2'

     @post = Post.documents.findOne @postId,
       transform: null # So that we can use test.equal


Refactored star drawing code.

Refactored star drawing code.

--- src/starshape.coffee
+++ src/starshape.coffee
@@ -203,23 +203,15 @@
   length_am * (1 / Math.cos(angle_cax))

-calculateInnerPoints = () ->
-  innerRadius = controls["inner-radius"].val()
+innerPoints = (radius) ->
   innerAngles = [-7*Math.PI/10, -3*Math.PI/10,   Math.PI/10, 5*Math.PI/10,  9*Math.PI/10]
-
-  innerAngles.map (angle) -> polarToCartesian(angle, innerRadius)
-
-innerPointStrings = () ->
-  calculateInnerPoints().map pointAsString
+  innerAngles.map (angle) -> polarToCartesian(angle, radius)

 outerAngles = () ->
   [-5*Math.PI/10,   -Math.PI/10, 3*Math.PI/10, 7*Math.PI/10, -9*Math.PI/10]

-calculateOuterPoints = (radius = 1) ->
+outerPoints = (radius = 1) ->
   outerAngles().map (angle) -> polarToCartesian(angle, radius)
-
-outerPointStrings = (radius = 1) ->
-  calculateOuterPoints(radius).map pointAsString

 # This functions calculates the points in between each set of inner and outer points.
 # If there are five points in each, there will be five in the output.
@@ -243,22 +235,66 @@
   calculateIntermediatePoints(innerPoints, shiftedInnerPoints, outerPoints, percentage)

-calculateLineToCurveTransitionPoints = (innerPoints, outerPoints) ->
-  percentage = controls["straight-percentage"].val()
-  calculateIntermediatePointsComingAndGoing(innerPoints, outerPoints, percentage)
-
-calculateControlPoints = (innerPoints, outerPoints) ->
-  percentage = controls["control-percentage"].val()
-  calculateIntermediatePointsComingAndGoing(innerPoints, outerPoints, percentage)
-
-calculateControlPointsFromIntermediate = (intermediatePoints1, intermediatePoints2, outerPoints) ->
-  percentage = controls["control-percentage"].val()
-  calculateIntermediatePoints(intermediatePoints1, intermediatePoints2, outerPoints, percentage)
-
-crossingCubicPoints = () ->
-  outerPoints = calculateOuterPoints()
-  controlAngle = controls["control-angle"].val()
-  controlDistance = controls["control-distance"].val()
+innerStarPoints = (innerRadius) ->
+  outer = outerPoints()
+  inner = innerPoints(innerRadius)
+
+  return [
+    outer.map(pointAsString)
+    inner.map(pointAsString)
+  ]
+
+linearStarPoints = innerStarPoints
+quadraticStarPoints = innerStarPoints
+
+innerAndIntermediatePoints = (innerRadius, percentage) ->
+  outer = outerPoints()
+  inner = innerPoints(innerRadius)
+  [intermediate1, intermediate2] = calculateIntermediatePointsComingAndGoing(inner, outer, percentage)
+
+  return [
+    outer.map(pointAsString)
+    inner.map(pointAsString)
+    intermediate1.map(pointAsString)
+    intermediate2.map(pointAsString)
+  ]
+
+starWithQuadraticTipsPoints = innerAndIntermediatePoints
+
+cubicStarPoints = (innerRadius, controlPercentage) ->
+  [outer, inner, control1, control2] = innerAndIntermediatePoints(innerRadius, controlPercentage)
+  return [inner, control1, control2]
+
+starWithCircularTipsPoints = (innerRadius, straightPercentage) ->
+  outer = outerPoints()
+  inner = innerPoints(innerRadius)
+  [intermediate1, intermediate2] = calculateIntermediatePointsComingAndGoing(inner, outer, straightPercentage)
+
+  radius = calculateRadius(intermediate1[0], outer[0], intermediate2[0])
+
+  return [
+    inner.map(pointAsString)
+    intermediate1.map(pointAsString)
+    intermediate2.map(pointAsString)
+    radius
+  ]
+
+starWithCubicTipsPoints = (innerRadius, straightPercentage, controlPercentage) ->
+  outer = outerPoints()
+  inner = innerPoints(innerRadius)
+  [intermediate1, intermediate2] = calculateIntermediatePointsComingAndGoing(inner, outer, straightPercentage)
+  [control1, control2] = calculateIntermediatePoints(intermediate1, intermediate2, outer, controlPercentage)
+
+  return [
+    inner.map(pointAsString)
+    intermediate1.map(pointAsString)
+    intermediate2.map(pointAsString)
+    control1.map(pointAsString)
+    control2.map(pointAsString)
+  ]
+
+crossingCubicPoints = (controlAngle, controlDistance) ->
+  outer = outerPoints()

   controlAngle = (controlAngle / 2) * (Math.PI / 180)
   angles = outerAngles()
@@ -269,107 +305,112 @@

   displacement1 = controlAngles1.map (angle) -> polarToCartesian(angle, controlDistance)
   displacement2 = controlAngles2.map (angle) -> polarToCartesian(angle, controlDistance)
-  controlPoints1 = _.zip(outerPoints, displacement1).map(addPoints)
-  controlPoints2 = _.zip(outerPoints, displacement2).map(addPoints)
-
-  [outerPoints.map(pointAsString), controlPoints1.map(pointAsString), controlPoints2.map(pointAsString)]
-
-
-drawLinearStar = () ->
-  innerPoints = innerPointStrings()
-  outerPoints = outerPointStrings()
-  points = _.flatten _.zip(innerPoints, outerPoints)
-
-  pathString = "M " + points.shift() + " L "
-  pathString += points.join(" ")
-  pathString += " Z"
-
-  $("#star").attr("d", pathString)
-
-drawQuadraticStar = () ->
-  innerPoints = innerPointStrings()
-  outerPoints = outerPointStrings()
-
-  firstPoint = innerPoints[0]
-  innerPoints.push(innerPoints.shift())
-  points = _.zip(outerPoints, innerPoints)
-
-  sectionStrings = points.map ([outerPoint, innerPoint]) ->
-    " Q " + outerPoint + " " + innerPoint
-
-  pathString = "M " + firstPoint + sectionStrings.join('') + " Z"
-  $("#star").attr("d", pathString)
-
-drawCubicStar = () ->
-  innerPoints = calculateInnerPoints()
-  outerPoints = calculateOuterPoints()
-  [controlPoints1, controlPoints2] = calculateControlPoints(innerPoints, outerPoints)
-
-  firstPoint = pointAsString(innerPoints[0])
-  innerPoints.push(innerPoints.shift())
-  points = _.zip(controlPoints1.map(pointAsString), controlPoints2.map(pointAsString), innerPoints.map(pointAsString))
+  controlPoints1 = _.zip(outer, displacement1).map(addPoints)
+  controlPoints2 = _.zip(outer, displacement2).map(addPoints)
+
+  [outer.map(pointAsString), controlPoints1.map(pointAsString), controlPoints2.map(pointAsString)]
+
+
+linearStarPath = (innerRadius) ->
+  [outer, inner] = linearStarPoints(innerRadius)
+
+  points = _.flatten(_.zip(inner, outer))
+  return "M " + points.join(" L ") + " Z"
+
+quadraticStarPath = (innerRadius) ->
+  [outer, inner] = quadraticStarPoints(innerRadius)
+
+  first = inner[0]
+  inner.push(inner.shift())
+  points = _.zip(outer, inner)
+
+  sectionStrings = points.map ([outer, inner]) ->
+    "Q #{ outer } #{ inner }"
+
+  return "M #{ first } " + sectionStrings.join(" ") + " Z"
+
+cubicStarPath = (innerRadius, controlPercentage) ->
+  [inner, control1, control2] = cubicStarPoints(innerRadius, controlPercentage)
+
+  first = inner[0]
+  inner.push(inner.shift())
+  points = _.zip(control1, control2, inner)

   sectionStrings = points.map ([control1, control2, inner]) ->
     "C #{ control1 } #{ control2 } #{ inner }"

-  pathString = "M #{ firstPoint } " + sectionStrings.join(" ") + " Z"
-  $("#star").attr("d", pathString)
-
-
-drawStarWithCircularTips = () ->
-  innerPoints = calculateInnerPoints()
-  outerPoints = calculateOuterPoints()
-  [intermediatePoints1, intermediatePoints2] = calculateLineToCurveTransitionPoints(innerPoints, outerPoints)
-
-  radius = calculateRadius(intermediatePoints1[0], outerPoints[0], intermediatePoints2[0])
-
-  points = _.zip(innerPoints.map(pointAsString), intermediatePoints1.map(pointAsString), intermediatePoints2.map(pointAsString))
+  return "M #{ first } " + sectionStrings.join(" ") + " Z"
+
+starWithCircularTipsPath = (innerRadius, straightPercentage) ->
+  [inner, intermediate1, intermediate2, radius] = starWithCircularTipsPoints(innerRadius, straightPercentage)
+  points = _.zip(inner, intermediate1, intermediate2)

   sectionStrings = points.map ([inner, intermediate1, intermediate2]) ->
     "#{ inner } L #{ intermediate1 } A #{ radius } #{ radius } 0 0 1 #{ intermediate2 }"
-  pathString = "M " + sectionStrings.join(" L ") + " Z"
-  $("#star").attr("d", pathString)
-
-drawStarWithQuadraticTips = () ->
-  if controls["straight-percentage"].val() == "0"
-    drawQuadraticStar()
-    return
-
-  innerPoints = calculateInnerPoints()
-  outerPoints = calculateOuterPoints()
-  [intermediatePoints1, intermediatePoints2] = calculateLineToCurveTransitionPoints(innerPoints, outerPoints)
-
-  points = _.zip(innerPoints.map(pointAsString), intermediatePoints1.map(pointAsString), outerPoints.map(pointAsString), intermediatePoints2.map(pointAsString))
+  return "M " + sectionStrings.join(" L ") + " Z"
+
+starWithQuadraticTipsPath = (innerRadius, straightPercentage) ->
+  if straightPercentage == "0"
+    return quadraticStarPath(innerRadius)
+
+  [outer, inner, intermediate1, intermediate2] = starWithQuadraticTipsPoints(innerRadius, straightPercentage)
+  points = _.zip(inner, intermediate1, outer, intermediate2)

   sectionStrings = points.map ([inner, intermediate1, outer, intermediate2]) ->
     "#{ inner } L #{ intermediate1 } Q #{ outer } #{ intermediate2 }"
-  pathString = "M " + sectionStrings.join(" L ") + " Z"
-  $("#star").attr("d", pathString)
-
-drawStarWithCubicTips = () ->
-  if controls["straight-percentage"].val() == "0"
-    drawCubicStar()
-    return
-
-  innerPoints = calculateInnerPoints()
-  outerPoints = calculateOuterPoints()
-  [intermediatePoints1, intermediatePoints2] = calculateLineToCurveTransitionPoints(innerPoints, outerPoints)
-  [controlPoints1, controlPoints2] = calculateControlPointsFromIntermediate(intermediatePoints1, intermediatePoints2, outerPoints)
-
-  points = _.zip(innerPoints.map(pointAsString), intermediatePoints1.map(pointAsString), controlPoints1.map(pointAsString), controlPoints2.map(pointAsString), intermediatePoints2.map(pointAsString))
+  return "M " + sectionStrings.join(" L ") + " Z"
+
+starWithCubicTipsPath = (innerRadius, straightPercentage, controlPercentage) ->
+  if straightPercentage == "0"
+    return cubicStarPath(innerRadius, controlPercentage)
+
+  [inner, intermediate1, intermediate2, control1, control2] = starWithCubicTipsPoints(innerRadius, straightPercentage, controlPercentage)
+  points = _.zip(inner, intermediate1, control1, control2, intermediate2)

   sectionStrings = points.map ([inner, intermediate1, control1, control2, intermediate2]) ->
     "#{ inner } L #{ intermediate1 } C #{ control1 } #{ control2 } #{ intermediate2 }"
-  pathString = "M " + sectionStrings.join(" L ") + " Z"
-  $("#star").attr("d", pathString)
-
+  return "M " + sectionStrings.join(" L ") + " Z"
+
+crossingCubicStarPath = (controlAngle, controlDistance) ->
+  [outer, control1, control2] = crossingCubicPoints(controlAngle, controlDistance)
+  "M #{outer[0]} C #{control1[0]} #{control2[2]} #{outer[2]}
+                 C #{control1[2]} #{control2[4]} #{outer[4]}
+                 C #{control1[4]} #{control2[1]} #{outer[1]}
+                 C #{control1[1]} #{control2[3]} #{outer[3]}
+                 C #{control1[3]} #{control2[0]} #{outer[0]}
+                 Z"
+
+
+setStarPath = (path) ->
+  $("#star").attr("d", path)
+  return
+
+drawLinearStar = () ->
+  innerRadius = controls["inner-radius"].val()
+  path = linearStarPath(innerRadius)
+  setStarPath(path)
+
+drawStarWithCircularTips = () ->
+  innerRadius = controls["inner-radius"].val()
+  straightPercentage = controls["straight-percentage"].val()
+  path = starWithCircularTipsPath(innerRadius, straightPercentage)
+  setStarPath(path)
+
+drawStarWithQuadraticTips = () ->
+  innerRadius = controls["inner-radius"].val()
+  straightPercentage = controls["straight-percentage"].val()
+  path = starWithQuadraticTipsPath(innerRadius, straightPercentage)
+  setStarPath(path)
+
+drawStarWithCubicTips = () ->
+  innerRadius = controls["inner-radius"].val()
+  straightPercentage = controls["straight-percentage"].val()
+  controlPercentage = controls["control-percentage"].val()
+  path = starWithCubicTipsPath(innerRadius, straightPercentage, controlPercentage)
+  setStarPath(path)

 drawCrossingCubicStar = () ->
-  [outer, control1, control2] = crossingCubicPoints()
-  pathString = "M #{outer[0]} C #{control1[0]} #{control2[2]} #{outer[2]}
-                              C #{control1[2]} #{control2[4]} #{outer[4]}
-                              C #{control1[4]} #{control2[1]} #{outer[1]}
-                              C #{control1[1]} #{control2[3]} #{outer[3]}
-                              C #{control1[3]} #{control2[0]} #{outer[0]}
-                              Z"
-  $("#star").attr("d", pathString)
+  controlAngle = controls["control-angle"].val()
+  controlDistance = controls["control-distance"].val()
+  path = crossingCubicStarPath(controlAngle, controlDistance)
+  setStarPath(path)


Client: Activity: Updated ChannelParticipantAvatars to delete unnecessary methods/lines. These methods come from DropboxInputMixin.

--- client/activity/lib/components/channelparticipantavatars/index.coffee
+++ client/activity/lib/components/channelparticipantavatars/index.coffee
@@ -7,13 +7,13 @@
 classnames                  = require 'classnames'
 AppFlux                     = require 'app/flux'
 ActivityFlux                = require 'activity/flux'
-KeyboardKeys                = require 'app/util/keyboardKeys'
 ProfileLinkContainer        = require 'app/components/profile/profilelinkcontainer'
 ChannelParticipantsDropdown = require 'activity/components/channelparticipantsdropdown'
+DropboxInputMixin           = require 'activity/components/dropbox/dropboxinputmixin'

 module.exports = class ChannelParticipantAvatars extends React.Component

-  { TAB, ESC, ENTER, UP_ARROW, DOWN_ARROW } = KeyboardKeys
+  @include [DropboxInputMixin]

   PREVIEW_COUNT     = 0
   MAX_PREVIEW_COUNT = 4
@@ -40,9 +40,6 @@
       selectedIndex      : getters.channelParticipantsSelectedIndex
       dropdownVisibility : getters.channelParticipantsDropdownVisibility
     }
-
-
-  getParticipantsDropdown: -> @refs.channelParticipantsDropdown

   componentDidMount: ->
@@ -207,54 +204,6 @@
     channel.setChannelParticipantsDropdownVisibility yes

-  onEnter: (event) ->
-
-    return  if event.shiftKey
-
-    kd.utils.stopDOMEvent event
-
-    dropdown = @getParticipantsDropdown()
-
-    if dropdown.isActive()
-
-      dropdown.confirmSelectedItem()
-
-
-  onEsc: (event) ->
-
-    @getParticipantsDropdown().close()
-
-
-  onNextPosition: (event, keyInfo) ->
-
-    dropdown = @getParticipantsDropdown()
-
-    if dropdown.isActive()
-
-      stopEvent = dropdown.moveToNextPosition keyInfo
-      kd.utils.stopDOMEvent event  if stopEvent
-
-
-  onPrevPosition: (event, keyInfo) ->
-
-    dropdown = @getParticipantsDropdown()
-
-    if dropdown.isActive()
-
-      stopEvent = dropdown.moveToPrevPosition keyInfo
-      kd.utils.stopDOMEvent event  if stopEvent
-
-
-  onKeyDown: (event) ->
-
-    switch event.which
-      when ENTER       then @onEnter event
-      when ESC         then @onEsc event
-      when TAB         then @onNextPosition event, { isTab : yes }
-      when DOWN_ARROW  then @onNextPosition event, { isDownArrow : yes }
-      when UP_ARROW    then @onPrevPosition event, { isUpArrow : yes }
-
-
   renderAddNewParticipantInput: ->

     <div className={@getNewParticipantInputClassNames()}>
@@ -288,7 +237,7 @@
   renderAddNewChannelParticipantsDropdown: ->

     <ChannelParticipantsDropdown
-      ref             = 'channelParticipantsDropdown'
+      ref             = 'dropdown'
       query           = { @state.query }
       value           = { @state.value }
       visible         = { @state.dropdownVisibility }


Tombstones: include Atom namespace in <updated/> element

Fixes tests "Retracting a local item should replace the item with a tombstone"
and "Retracting a local item should be notified to subscribers".

--- src/tombstone.coffee
+++ src/tombstone.coffee
@@ -10,7 +10,7 @@
     ref = item.getChild('link', NS_ATOM).attrs.href
     now = new Date().toISOString()
     tsEl = new Element('deleted-entry', xmlns: NS_AT, ref: ref, when: now).
-        c("updated").t(now).up()
+        c("updated", xmlns: NS_ATOM).t(now).up()

     children = []
     children.push(item.getChild(name, NS_ATOM)?.attr('xmlns', NS_ATOM)) for name in ['id', 'link', 'published']


真·Fix dkbug

真·Fix dkbug

--- index.cjsx
+++ index.cjsx
@@ -405,7 +405,7 @@
   reactClass: React.createClass
     getInitialState: ->
       # Load map data
-      mapspot = CSON.parseCSONFile join(__dirname, 'assets', 'data', 'mapspot.cson')
+      mapspot = null
       mainFleet: new Fleet()
       enemyFleet: new Fleet()
       escortFleet: new Fleet()
@@ -437,7 +437,7 @@
       fs.readFileAsync(join(__dirname, 'assets', 'data', 'mapspot.cson'))
         .then (data) =>
           mapspot = CSON.parseCSONString data
-          @setState {mapspot}
+          @setState {MAPSPOT: mapspot}
           store.dispatch
             type: '@@poi-plugin-prophet/updateMapspot'
             data: mapspot


rework long-polling server to be a valid EventSource

this is quite a bit of code change. I think I'll keep the code in the
talk on the level of the old version as that's much more readable

--- long-polling/lib/server.coffee
+++ long-polling/lib/server.coffee
@@ -11,10 +11,9 @@
 pg.defaults.poolSize = 5

 ok = (res, headers)->
-  h = 'Content-Type': 'application/json'
   for own k, v of headers
-    h[k] = v
-  res.writeHead 200, h
+    res.setHeader k, v
+  res.writeHead 200

 notfound = (res)->
   res.writeHead 404, 'Content-Type:': 'text/plain'
@@ -34,14 +33,13 @@
   e.data = tryparse e.data
   e

-with_postgres = (db, cb)->
+with_postgres = (cb)->
   as = cfg['psql-access-string']
-  as = as + db if db
   pg.connect as, (err, client)->
     cb(err, client)

-query = (db, query, params, cb)->
-  with_postgres db, (err, client)->
+query = (query, params, cb)->
+  with_postgres (err, client)->
     return cb(err, null) if err
     client.query query, params, (err, res)->
       return cb(err, null) if err
@@ -49,22 +47,50 @@
       client.end()

-events_since_id = (db, channel, id, cb)->
+events_since_id = (channel, id, cb)->
   q = """
       select * from events
       where channel_id = $1 and id > $2
       order by id asc
       """
-  query db, q, [channel, id], cb
+  query q, [channel, id], cb

-events_since_time = (db, channel, ts, cb)->
+events_since_time = (channel, ts, cb)->
   q = """
       select * from events o
       where channel_id = $1
       and ts > (SELECT TIMESTAMP WITH TIME ZONE 'epoch' + $2 * INTERVAL '1 second')
       order by id asc
       """
-  query db, q, [channel, ts], cb
+  query q, [channel, ts], cb
+
+
+handler = (channel, last_event_id, cb)->
+    if last_event_id.substr(0, 3) == 'rt-'
+      last_event_id = last_event_id.substr(3)
+      events_since_time(channel, last_event_id, cb)
+    else
+      events_since_id(channel, last_event_id, cb)
+
+write_long_poll = (response, events, add_headers)->
+  unless response.headersSent
+    response.setHeader 'Content-Type', 'application/json'
+    ok response, add_headers
+  response.end JSON.stringify events
+  false
+
+write_eventsource = (response, events, add_headers)->
+  unless response.headersSent
+    response.setHeader 'Content-Type', 'text/event-stream'
+    ok response, add_headers
+
+  for event in events
+    response.write ["event: "+event.data.type,
+      "data: " + JSON.stringify(event.data.data),
+      "id: " + event.id,
+      "", ""].join "\n"
+
+  true

 waiting_channels = {}
 listen_port = cfg.listen_port ? 9984
@@ -77,18 +103,10 @@
     url = require("url").parse req.url, true
     [channel, wkey] = url.pathname.substr(1).split '/'
     last_event_id = req.headers['last-event-id'] or url.query['last_event_id']
-    db = null
-    if (cfg['dynamic-database'])
-      db = req.headers['ps-dynamic-database'] ? 'popscan'
-      wkey = db + wkey
+    flavor = req.headers['flavor'] or url.query['flavor'] or 'long-poll'

-    return http_error res, 400, 'missing last-event-id' unless last_event_id
-
-    if last_event_id.substr(0, 3) == 'rt-'
-      fun = events_since_time
-      last_event_id = last_event_id.substr(3)
-    else
-      fun = events_since_id
+    return http_error res, 400, 'unknown flavor' unless flavor in ['long-poll', 'eventsource']
+    write = if flavor == "long-poll" then write_long_poll else write_eventsource

     clear_waiting = ()->
       delete waiting_channels[wkey] if wkey
@@ -102,30 +120,45 @@
     req.on 'close', ->
       clear_waiting()

-    fun db, channel, last_event_id, (err, evts)->
-      return http_error res, 500, 'Failed to get event data: ' + err if err
+    close_redis = (client)->
+      client.close()

-      if waiting() or (evts and evts.length > 0)
-        ok res,
-          'x-shortpoll-info': 'true'
-          'x-ps-reconnect-in': if waiting() then 5 else 0
-        return res.end JSON.stringify evts

-      c = redis.createClient 6379, cfg['redis-server'] || 'localhost'
-      req.on 'close', ->
-        c.end()
-      c.select cfg['db'] || 2, (err, r)->
-        set_waiting()
-        c.subscribe 'channels:'+channel
-        c.on "message", (chn, message)->
-          fun db, channel, last_event_id, (err, evts)->
-            c.end()
-            return http_error 500, 'Failed to get event data' if err
-            ok res,
-              'x-shortpoll-info': 'false'
-              'x-ps-reconnect-in': 0
-            res.end JSON.stringify evts
-            clear_waiting()
+    handle_request = ()->
+
+      handler channel, last_event_id, (err, evts)->
+
+        return http_error res, 500, 'Failed to get event data: ' + err if err
+
+        last_event_id = evts[evts.length-1].id if (evts and evts.length > 0)
+
+        if waiting() or (evts and evts.length > 0)
+          return unless write(res, evts,
+            'x-shortpoll-info': 'true'
+            'x-ps-reconnect-in': if waiting() then 5 else 0)
+
+        c = redis.createClient 6379, cfg['redis-server'] || 'localhost'
+        close_redis = ()-> c.end()
+
+        req.on 'close', close_redis
+
+        c.select cfg['db'] || 2, (err, r)->
+          set_waiting()
+          c.subscribe 'channels:'+channel
+          c.on "message", (chn, message)->
+            handler channel, last_event_id, (err, evts)->
+              c.end()
+              req.removeListener 'close', close_redis
+              return http_error 500, 'Failed to get event data' if err
+
+              continue_listening = write res, evts,
+                'x-shortpoll-info': 'false'
+                'x-ps-reconnect-in': 0
+              return clear_waiting() unless continue_listening
+              last_event_id = evts[evts.length-1].id if (evts and evts.length > 0)
+              process.nextTick ()->
+                handle_request()
+    handle_request()
   ).listen listen_port, listen_address

   console.log "Server running at http://#{listen_address}:#{listen_port}/"


Working on bounding box selection

Working on bounding box selection

--- src/ubret/map.coffee
+++ src/ubret/map.coffee
@@ -44,6 +44,12 @@
       @settings
         center_lat: center.lat
         center_lng: center.lng
+
+      bounds = @map.getBounds()
+      console.log bounds
+      {_northEast: {lat, lng}, _southWest: {lat, lng} = bounds
+      console.log _northEast
+      #selection = _(@opts.data).chain().filter((d) ->

   createSky: (spectrum) =>
     unless @map
@@ -91,18 +97,23 @@
   plotObject: (subject, options) =>
     coords = [subject.dec, subject.ra - 180]
     options =
-      icon = new L.icon {
-          iconSize: [25, 41]
-          iconAnchor: [13, 41]
-        }
-
-    circle = new L.marker(coords, options)
+      icon = new L.icon
+        iconSize: [25, 41]
+        iconAnchor: [13, 41]
+        iconUrl: "marker-icon.png"
+
+    selectedOptions =
+      icon =  new L.icon
+        iconSize: [25, 41]
+        iconAnchor: [13, 41]
+        iconUrl: "marker-icon.png"
+
+    if subject.uid in @opts.selectedIds
+      circle = new L.marker(coords, selectedOptions)
+    else
+      circle = new L.marker(coords, options)
     circle.uid = subject.uid
-
     circle.addTo(@map)
-      # .bindPopup('etc')
-      # .openPopup()
-
     @circles.push circle

   plotObjects: =>
@@ -110,7 +121,4 @@
     @circles = new Array
     @plotObject subject for subject in @opts.data

-    latlng = new L.LatLng(@opts.data[0].dec, @opts.data[0].ra)
-    @map.panTo latlng
-
 window.Ubret.Mapper = Mapper


One more test.

One more test.

--- tests.coffee
+++ tests.coffee
@@ -7,6 +7,7 @@
     @assertEqual foo(), 42
     @assertInstanceOf foo, ReactiveField
     @assertEqual foo.constructor, ReactiveField
+    @assertTrue _.isFunction foo
     @assertEqual foo(43), 43
     @assertEqual foo(), 43


More fixing of tests.

More fixing of tests.

--- tests.coffee
+++ tests.coffee
@@ -7,8 +7,7 @@
 assert _.isEqual Document.list, []
 assert _.isEqual Document._collections, {}

-if Meteor.isServer
-  globalTestTriggerCounters = {}
+globalTestTriggerCounters = {}

 class Post extends Document
   # Other fields:
@@ -370,15 +369,13 @@
       handles = []
       for document in Document.list
         do (document) ->
-          initializing = true
           handles.push document.documents.find({}).observeChanges
             added: (id, fields) ->
-              newTimeout() unless initializing
+              newTimeout()
             changed: (id, fields) ->
               newTimeout()
             removed: (id) ->
               newTimeout()
-          initializing = false
       future.wait()
       for handle in handles
         handle.stop()
@@ -20906,6 +20903,10 @@
     waitForDatabase test, expect
 ,
   (test, expect) ->
+    # We sleep a bit so that all changes get to the client, when rnning on the client.
+    Meteor.setTimeout expect(), 200
+,
+  (test, expect) ->
     docs = Recursive.documents.find(
       _id:
         $in: _.pluck(@testContent, '_id')


Add save and save-all to toolbar

Add save and save-all to toolbar

--- toolbar.cson
+++ toolbar.cson
@@ -6,7 +6,7 @@
     tooltip: "New File"
     iconset: "ion"
     style:
-      color: "tan"
+      color: "teal"
   }
   {
     type: "button"
@@ -15,7 +15,29 @@
     tooltip: "Open..."
     callback: "application:open-file"
     style:
-      color: "tan"
+      color: "teal"
+  }
+  {
+    type: "spacer"
+  }
+  {
+    type: "button"
+    iconset: "mdi"
+    icon: "content-save"
+    style:
+      color: "teal"
+    callback: "core:save"
+  }
+  {
+    type: "button"
+    iconset: "mdi"
+    icon: "content-save-all"
+    style:
+      color: "teal"
+    callback: "window:save-all"
+  }
+  {
+    type: "spacer"
   }
   {
     type: "spacer"


coordinates debugger

coordinates debugger

--- src/ui/canvas.coffee
+++ src/ui/canvas.coffee
@@ -1,6 +1,7 @@
 app.canvas = (options) ->
   map = options.map
   bbox = map.db.bbox
+  map.debug.coordinates = false

   extent = {
     w: d3.max([-bbox[0], bbox[2]]) * 2
@@ -118,6 +119,12 @@

     mapLayer.attr('style', style)

+    if map.debug.coordinates
+      uiLayer.on 'mousemove', ->
+        console.log(proj.inverse(projection.invert([d3.event.x, d3.event.y])))
+    else
+      uiLayer.on 'mousemove', ->
+
   map.centerAt = (pos, new_sc) ->
     updateProjection(new_sc, [0, 0])
     xy = projection(pos)


Added todo

Added todo

--- spec/ApiClient-spec.coffee
+++ spec/ApiClient-spec.coffee
@@ -108,3 +108,6 @@

       expect(status.value.error).toBe('Permission Denied')
       expect(status.value.info).toBe('I didn\'t recognize that core name or ID')
+
+
+  # TODO: Test offline


simplify a variable name since it's now clear what its context is

--- source/javascripts/index/holidayTableFactory.coffee
+++ source/javascripts/index/holidayTableFactory.coffee
@@ -38,8 +38,8 @@
     zmanim.hadlakatNerot().format('h:mm')

 seudat_shelishit_in_shul_description = (hebrewDate) ->
-  seudat_shelishit_verb = if hebrewDate.isErebShabuot() || hebrewDate.isEreb9Ab() then "Finish" else "Begin"
-  "#{seudat_shelishit_verb} סְעוּדַת שְׁלִישִׁית before"
+  verb = if hebrewDate.isErebShabuot() || hebrewDate.isEreb9Ab() then "Finish" else "Begin"
+  "#{verb} סְעוּדַת שְׁלִישִׁית before"

 rabbenuTamTime = (zmanim) -> moment(zmanim.sunset()).add(72, 'minutes').seconds(60).format('h:mm')


Add Spec T002-002 for Label::Models::Label

--- spec/src/coffee/label/models/t002_label_spec.coffee
+++ spec/src/coffee/label/models/t002_label_spec.coffee
@@ -13,105 +13,264 @@
     backbone.ajax = jQuery.ajax

   describe "001: name", ->
-    beforeEach ->
-      nock("http://localhost:8888")
-        .intercept("/user_name/repo_name", "POST")
-        .reply(
-          200
-          (url, body)->
-            type: "post"
-        )
-      nock("http://localhost:8888")
-        .intercept("/user_name/repo_name", "PUT")
-        .reply(
-          200
-          (url, body)->
-            type: "put"
-        )
-      nock("http://localhost:8888")
-        .intercept("/user_name/repo_name", "DELETE")
-        .reply(
-          200
-          (url, body)->
-            type: "delete"
-        )
-      @label_url = "http://localhost:8888/user_name/repo_name"
-      @spy = sinon.spy()
-
-    it "001: create", (done)->
-      label1 = new @Label()
-      label1.on "sync", =>
-        @spy "sync"
-      label1.url = @label_url
-      label1.set "name", "category1/child-category1/label1"
-      label1.get("name").should.equal "category1/child-category1/label1"
-      label1.save(
-        {
-          name: "category1/child-category2/label1"
-        }
-        {
-          success:
-            (model, ret)=>
-              setTimeout(
-                =>
-                  ret.type.should.equal "post"
-                  label1.get("name").should.equal "category1/child-category2/label1"
-                  @spy.calledWith("sync").should.equal true
-                  done()
-                0
-              )
-        }
-      )
-
-    it "002: update", (done)->
-      label1 = new @Label(
-        id: "label1"
-      )
-      label1.on "sync", =>
-        @spy "sync"
-      label1.url = @label_url
-      label1.set "name", "category1/child-category1/label1"
-      label1.get("name").should.equal "category1/child-category1/label1"
-      label1.save(
-        {
-          name: "category1/child-category2/label1"
-        }
-        {
-          success:
-            (model, ret)=>
-              setTimeout(
-                =>
-                  ret.type.should.equal "put"
-                  label1.get("name").should.equal "category1/child-category2/label1"
-                  @spy.calledWith("sync").should.equal true
-                  done()
-                0
-              )
-        }
-      )
-
-    it "003: delete", (done)->
-      label1 = new @Label(
-        id: "label1"
-      )
-      label1.on "sync", =>
-        @spy "sync"
-      label1.on "destroy", =>
-        @spy "destroy"
-      label1.url = @label_url
-      label1.set "name", "カテゴリ1/子カテゴリ1/ラベル1"
-      label1.get("name").should.equal "カテゴリ1/子カテゴリ1/ラベル1"
-      label1.destroy(
-        success: (model, ret)=>
-          setTimeout(
-            =>
-              ret.type.should.equal "delete"
-              @spy.calledWith("sync").should.equal true
-              @spy.calledWith("destroy").should.equal true
-              done()
-            0
-          )
-      )
+    describe "001: event", ->
+      beforeEach ->
+        nock("http://localhost:8888")
+          .intercept("/user_name/repo_name", "POST")
+          .reply(
+            200
+            (url, body)->
+              type: "post"
+          )
+        nock("http://localhost:8888")
+          .intercept("/user_name/repo_name", "PUT")
+          .reply(
+            200
+            (url, body)->
+              type: "put"
+          )
+        nock("http://localhost:8888")
+          .intercept("/user_name/repo_name", "DELETE")
+          .reply(
+            200
+            (url, body)->
+              type: "delete"
+          )
+        @label_url = "http://localhost:8888/user_name/repo_name"
+        @spy = sinon.spy()
+
+      it "001: create", (done)->
+        label1 = new @Label()
+        label1.on "sync", =>
+          @spy "sync"
+        label1.url = @label_url
+        label1.set "name", "category1/child-category1/label1"
+        label1.get("name").should.equal "category1/child-category1/label1"
+        label1.save(
+          {
+            name: "category1/child-category2/label1"
+          }
+          {
+            success:
+              (model, ret)=>
+                setTimeout(
+                  =>
+                    ret.type.should.equal "post"
+                    label1.get("name").should.equal "category1/child-category2/label1"
+                    @spy.calledWith("sync").should.equal true
+                    done()
+                  0
+                )
+          }
+        )
+
+      it "002: update", (done)->
+        label1 = new @Label(
+          id: "label1"
+        )
+        label1.on "sync", =>
+          @spy "sync"
+        label1.url = @label_url
+        label1.set "name", "category1/child-category1/label1"
+        label1.get("name").should.equal "category1/child-category1/label1"
+        label1.save(
+          {
+            name: "category1/child-category2/label1"
+          }
+          {
+            success:
+              (model, ret)=>
+                setTimeout(
+                  =>
+                    ret.type.should.equal "put"
+                    label1.get("name").should.equal "category1/child-category2/label1"
+                    @spy.calledWith("sync").should.equal true
+                    done()
+                  0
+                )
+          }
+        )
+
+      it "003: delete", (done)->
+        label1 = new @Label(
+          id: "label1"
+        )
+        label1.on "sync", =>
+          @spy "sync"
+        label1.on "destroy", =>
+          @spy "destroy"
+        label1.url = @label_url
+        label1.set "name", "カテゴリ1/子カテゴリ1/ラベル1"
+        label1.get("name").should.equal "カテゴリ1/子カテゴリ1/ラベル1"
+        label1.destroy(
+          success: (model, ret)=>
+            setTimeout(
+              =>
+                ret.type.should.equal "delete"
+                @spy.calledWith("sync").should.equal true
+                @spy.calledWith("destroy").should.equal true
+                done()
+              0
+            )
+        )

   describe "002: color", ->
-
+    describe "001: event", ->
+      beforeEach ->
+        nock("http://localhost:8888")
+          .intercept("/user_name/repo_name", "POST")
+          .reply(
+            200
+            (url, body)->
+              type: "post"
+          )
+        nock("http://localhost:8888")
+          .intercept("/user_name/repo_name", "PUT")
+          .reply(
+            200
+            (url, body)->
+              type: "put"
+          )
+        nock("http://localhost:8888")
+          .intercept("/user_name/repo_name", "DELETE")
+          .reply(
+            200
+            (url, body)->
+              type: "delete"
+          )
+        @label_url = "http://localhost:8888/user_name/repo_name"
+        @spy = sinon.spy()
+
+      it "001: validation: #FF0000", (done)->
+        label1 = new @Label()
+        label1.url = @label_url
+        label1.on "sync", =>
+          @spy "sync"
+        label1.save(
+          {
+            color: "#FF0000"
+          }
+          {
+            success: =>
+              setTimeout(
+                =>
+                  label1.get_color().should.equal "#FF0000"
+                  @spy.calledWith("sync").should.equal true
+                  done()
+                0
+              )
+          }
+        )
+
+      it "002: validation: {r: 255, g: 0, b: 0}", ->
+        label1 = new @Label()
+        label1.url = @label_url
+        label1.on "sync", =>
+          @spy "sync"
+        label1.save(
+          {
+            color: {
+              r: 255
+              g: 0
+              b: 0
+            }
+          }
+          {
+            success: =>
+              setTimeout(
+                =>
+                  label1.get_color().should.equal "#FF0000"
+                  @spy.calledWith("sync").should.equal true
+                  done()
+                0
+              )
+          }
+        )
+
+      it "003: validation: FF0000 -> #FF0000", ->
+        label1 = new @Label()
+        label1.on "sync", =>
+          @spy "sync"
+        label1.save(
+          {
+            color: "FF0000"
+          }
+          {
+            success: =>
+              setTimeout(
+                =>
+                  label1.get_color().should.equal "#FF0000"
+                  @spy.calledWith("sync").should.equal true
+                  done()
+                0
+              )
+          }
+        )
+
+      it "004: invalidation: FF00000", ->
+        label1 = new @Label()
+        label1.url = @label_url
+        label1.on "sync", =>
+          @spy "sync"
+        label1.on "invalid", =>
+          @spy "invalid"
+        label1.save(
+          {
+            color: "FF00000"
+          }
+          {
+            success: =>
+              setTimeout(
+                =>
+                  label1.get_color().should.equal "#FF0000"
+                  @spy.calledWith("sync").should.equal false
+                  @spy.calledWith("invalid").should.equal true
+                  done()
+                0
+              )
+          }
+        )
+
+      it "005: validation: F00 -> #FF0000", ->
+        label1 = new @Label()
+        label1.url = @label_url
+        label1.on "sync", =>
+          @spy "sync"
+        label1.save(
+          {
+            color: "F00"
+          }
+          {
+            success: =>
+              setTimeout(
+                =>
+                  label1.get_color().should.equal "#FF0000"
+                  @spy.calledWith("sync").should.equal true
+                  done()
+                0
+              )
+          }
+        )
+
+      it "006: validation: #F00 -> #FF0000", ->
+        label1 = new @Label()
+        label1.url = @label_url
+        label1.on "sync", =>
+          @spy "sync"
+        label1.save(
+          {
+            color: "#F00"
+          }
+          {
+            success: =>
+              setTimeout(
+                =>
+                  label1.get_color().should.equal "#FF0000"
+                  @spy.calledWith("sync").should.equal true
+                  done()
+                0
+              )
+          }
+        )
+
+


QUALITY: Remove debug message from TaskInboxStateCtrl

--- src/app/units/states/tasks/inbox/inbox.coffee
+++ src/app/units/states/tasks/inbox/inbox.coffee
@@ -18,5 +18,4 @@

 .controller('TaskInboxStateCtrl', ($scope, Unit) ->
   $scope.taskSource = Unit.tasksForTaskInbox
-  console.log("Set dataSource to ", $scope.dataSource)
 )


More tests.

More tests.

--- tests.coffee
+++ tests.coffee
@@ -90,13 +90,23 @@

     foo()

+    @assertTrue foo._isRunning()
+
     Tracker.flush()
+
+    @assertFalse foo._isRunning()

     foo()

+    @assertTrue foo._isRunning()
+
     Tracker.flush()

+    @assertFalse foo._isRunning()
+
     foo()
+
+    @assertTrue foo._isRunning()

     @assertEqual run, [42, 42, 42]

@@ -112,13 +122,23 @@

     foo()

+    @assertTrue foo._isRunning()
+
     Tracker.flush()
+
+    @assertTrue foo._isRunning()

     foo()

+    @assertTrue foo._isRunning()
+
     Tracker.flush()

+    @assertTrue foo._isRunning()
+
     foo()
+
+    @assertTrue foo._isRunning()

     @assertEqual run, [42]


showing that you can assign the quote symbol ' the "@" so you can use the standard quote symbol if you want to


Fixing a treeView scroll spec

Fixing a treeView scroll spec

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -507,7 +507,7 @@

         entryCount = treeView.find(".entry").length
         _.times entryCount, -> treeView.moveDown()
-        expect(treeView.scrollBottom() + 2).toBe treeView.prop('scrollHeight')
+        expect(treeView.scrollBottom()).toBe treeView.prop('scrollHeight')

         _.times entryCount, -> treeView.moveUp()
         expect(treeView.scrollTop()).toBe 0


Remove a console.log

Remove a console.log

--- admin/static/coffee/namespaces/index.coffee
+++ admin/static/coffee/namespaces/index.coffee
@@ -93,7 +93,6 @@

         # Callback that will be registered: updates the toolbar buttons based on how many namespaces have been selected
         update_toolbar_buttons: =>
-            console.log @get_selected_namespaces()
             if @get_selected_namespaces().length < 1
                 @.$('.btn.remove-namespace').attr 'disabled', 'disabled'
             else


Remove stray log

Remove stray log

--- src/workspace.coffee
+++ src/workspace.coffee
@@ -62,7 +62,6 @@

     params.viewRegistry = new ViewRegistry
     params.paneContainer.viewRegistry = params.viewRegistry
-    console.log "deserializing pane container"
     params.paneContainer = PaneContainer.deserialize(params.paneContainer)
     params


Only detach with toggle keybinding

Only detach with toggle keybinding

--- src/packages/editor-stats/src/editor-stats-view.coffee
+++ src/packages/editor-stats/src/editor-stats-view.coffee
@@ -12,8 +12,6 @@

   initialize: (@rootView) ->
     super
-
-    @command 'core:cancel', @detach

   draw: ->
     @x ?= d3.scale.ordinal().domain d3.range(@stats.hours * 60)


ADD allow to pass options using .load_module()

When calling `Darwin.Loader.load_module()` manually, it is now possible
to pass options for controller :

    Darwin.Loader.load_module( $block.data( 'module' ), $block, View: App.Views.MyCustomView )

--- app/assets/javascripts/darwin/loader.coffee
+++ app/assets/javascripts/darwin/loader.coffee
@@ -11,14 +11,14 @@
     )

-  load_module: ( pathname, $root ) ->
+  load_module: ( pathname, $root, options ) ->
     module_name = loader.compute_name( pathname )
     path        = pathname.split( '.' )
     module      = App.Controllers
     module      = module[ path.shift() ] while path.length

     if module
-      controllers[ module_name ]    = new module( $root )
+      controllers[ module_name ]    = new module( $root, options )
       controllers[ module_name ].id = module_name
     else
       throw new Error( "Can't find module #{pathname}" )