Adding more snippets

Adding more snippets

--- snippets/select.cson
+++ snippets/select.cson
@@ -149,152 +149,152 @@
     'prefix': 'select alias option number "option number" from the drop down list with the css selector of alias "indentifier" if it exists'
     'body': 'select alias "$1" from the drop down list with the css selector of alias "$2" if it exists'

-'select101':
-  'prefix': 'select "option text" from the drop down list found by "identifier"'
-  'body': 'select "$1" from the drop down list found by "$2"'
-'select102':
-  'prefix': 'select "option text" from the drop down list found by alias "identifier"'
-  'body': 'select "$1" from the drop down list found by alias "$2"'
-'select103':
-  'prefix': 'select "option text" from the drop down list found by "identifier" if it exists'
-  'body': 'select "$1" from the drop down list found by "$2" if it exists'
-'select104':
-  'prefix': 'select "option text" from the drop down list found by alias "identifier" if it exists'
-  'body': 'select "$1" from the drop down list found by alias "$2" if it exists'
-'select105':
-  'prefix': 'select alias "option text" from the drop down list found by "identifier"'
-  'body': 'select alias "$1" from the drop down list found by "$2"'
-'select106':
-  'prefix': 'select alias "option text" from the drop down list found by alias "identifier"'
-  'body': 'select alias "$1" from the drop down list found by alias "$2"'
-'select107':
-  'prefix': 'select alias "option text" from the drop down list found by "identifier" if it exists'
-  'body': 'select alias "$1" from the drop down list found by "$2" if it exists'
-'select108':
-  'prefix': 'select alias "option text" from the drop down list found by alias "identifier" if it exists'
-  'body': 'select alias "$1" from the drop down list found by alias "$2" if it exists'
-
-'select110':
-  'prefix': 'select "option text" from the drop down list with the ID of "indentifier"'
-  'body': 'select "$1" from the drop down list with the ID of "$2"'
-'select111':
-  'prefix': 'select "option text" from the drop down list with the ID of alias "indentifier"'
-  'body': 'select "$1" from the drop down list with the ID of alias "$2"'
-'select112':
-  'prefix': 'select "option text" from the drop down list with the ID of "indentifier" if it exists'
-  'body': 'select "$1" from the drop down list with the ID of "$2" if it exists'
-'select113':
-  'prefix': 'select "option text" from the drop down list with the ID of alias "indentifier" if it exists'
-  'body': 'select "$1" from the drop down list with the ID of alias "$2" if it exists'
-'select114':
-  'prefix': 'select alias "option text" from the drop down list with the ID of "indentifier"'
-  'body': 'select alias "$1" from the drop down list with the ID of "$2"'
-'select115':
-  'prefix': 'select alias "option text" from the drop down list with the ID of alias "indentifier"'
-  'body': 'select alias "$1" from the drop down list with the ID of alias "$2"'
-'select116':
-  'prefix': 'select alias "option text" from the drop down list with the ID of "indentifier" if it exists'
-  'body': 'select alias "$1" from the drop down list with the ID of "$2" if it exists'
-'select117':
-  'prefix': 'select alias "option text" from the drop down list with the ID of alias "indentifier" if it exists'
-  'body': 'select alias "$1" from the drop down list with the ID of alias "$2" if it exists'
-
-'select120':
-  'prefix': 'select "option text" from the drop down list with the class of "indentifier"'
-  'body': 'select "$1" from the drop down list with the class of "$2"'
-'select121':
-  'prefix': 'select "option text" from the drop down list with the class of alias "indentifier"'
-  'body': 'select "$1" from the drop down list with the class of alias "$2"'
-'select122':
-  'prefix': 'select "option text" from the drop down list with the class of "indentifier" if it exists'
-  'body': 'select "$1" from the drop down list with the class of "$2" if it exists'
-'select123':
-  'prefix': 'select "option text" from the drop down list with the class of alias "indentifier" if it exists'
-  'body': 'select "$1" from the drop down list with the class of alias "$2" if it exists'
-'select124':
-  'prefix': 'select alias "option text" from the drop down list with the class of "indentifier"'
-  'body': 'select alias "$1" from the drop down list with the class of "$2"'
-'select125':
-  'prefix': 'select alias "option text" from the drop down list with the class of alias "indentifier"'
-  'body': 'select alias "$1" from the drop down list with the class of alias "$2"'
-'select126':
-  'prefix': 'select alias "option text" from the drop down list with the class of "indentifier" if it exists'
-  'body': 'select alias "$1" from the drop down list with the class of "$2" if it exists'
-'select127':
-  'prefix': 'select alias "option text" from the drop down list with the class of alias "indentifier" if it exists'
-  'body': 'select alias "$1" from the drop down list with the class of alias "$2" if it exists'
-
-'select130':
-  'prefix': 'select "option text" from the drop down list with the xpath of "indentifier"'
-  'body': 'select "$1" from the drop down list with the xpath of "$2"'
-'select131':
-  'prefix': 'select "option text" from the drop down list with the xpath of alias "indentifier"'
-  'body': 'select "$1" from the drop down list with the xpath of alias "$2"'
-'select132':
-  'prefix': 'select "option text" from the drop down list with the xpath of "indentifier" if it exists'
-  'body': 'select "$1" from the drop down list with the xpath of "$2" if it exists'
-'select133':
-  'prefix': 'select "option text" from the drop down list with the xpath of alias "indentifier" if it exists'
-  'body': 'select "$1" from the drop down list with the xpath of alias "$2" if it exists'
-'select134':
-  'prefix': 'select alias "option text" from the drop down list with the xpath of "indentifier"'
-  'body': 'select alias "$1" from the drop down list with the xpath of "$2"'
-'select135':
-  'prefix': 'select alias "option text" from the drop down list with the xpath of alias "indentifier"'
-  'body': 'select alias "$1" from the drop down list with the xpath of alias "$2"'
-'select136':
-  'prefix': 'select alias "option text" from the drop down list with the xpath of "indentifier" if it exists'
-  'body': 'select alias "$1" from the drop down list with the xpath of "$2" if it exists'
-'select137':
-  'prefix': 'select alias "option text" from the drop down list with the xpath of alias "indentifier" if it exists'
-  'body': 'select alias "$1" from the drop down list with the xpath of alias "$2" if it exists'
-
-'select140':
-  'prefix': 'select "option text" from the drop down list with the name of "indentifier"'
-  'body': 'select "$1" from the drop down list with the name of "$2"'
-'select141':
-  'prefix': 'select "option text" from the drop down list with the name of alias "indentifier"'
-  'body': 'select "$1" from the drop down list with the name of alias "$2"'
-'select142':
-  'prefix': 'select "option text" from the drop down list with the name of "indentifier" if it exists'
-  'body': 'select "$1" from the drop down list with the name of "$2" if it exists'
-'select143':
-  'prefix': 'select "option text" from the drop down list with the name of alias "indentifier" if it exists'
-  'body': 'select "$1" from the drop down list with the name of alias "$2" if it exists'
-'select144':
-  'prefix': 'select alias "option text" from the drop down list with the name of "indentifier"'
-  'body': 'select alias "$1" from the drop down list with the name of "$2"'
-'select145':
-  'prefix': 'select alias "option text" from the drop down list with the name of alias "indentifier"'
-  'body': 'select alias "$1" from the drop down list with the name of alias "$2"'
-'select146':
-  'prefix': 'select alias "option text" from the drop down list with the name of "indentifier" if it exists'
-  'body': 'select alias "$1" from the drop down list with the name of "$2" if it exists'
-'select147':
-  'prefix': 'select alias "option text" from the drop down list with the name of alias "indentifier" if it exists'
-  'body': 'select alias "$1" from the drop down list with the name of alias "$2" if it exists'
-
-'select150':
-  'prefix': 'select "option text" from the drop down list with the css selector of "indentifier"'
-  'body': 'select "$1" from the drop down list with the name of "$2"'
-'select151':
-  'prefix': 'select "option text" from the drop down list with the css selector of alias "indentifier"'
-  'body': 'select "$1" from the drop down list with the name of alias "$2"'
-'select152':
-  'prefix': 'select "option text" from the drop down list with the css selector of "indentifier" if it exists'
-  'body': 'select "$1" from the drop down list with the name of "$2" if it exists'
-'select153':
-  'prefix': 'select "option text" from the drop down list with the css selector of alias "indentifier" if it exists'
-  'body': 'select "$1" from the drop down list with the css selector of alias "$2" if it exists'
-'select154':
-  'prefix': 'select alias "option text" from the drop down list with the css selector of "indentifier"'
-  'body': 'select alias "$1" from the drop down list with the css selector of "$2"'
-'select155':
-  'prefix': 'select alias "option text" from the drop down list with the css selector of alias "indentifier"'
-  'body': 'select alias "$1" from the drop down list with the css selector of alias "$2"'
-'select156':
-  'prefix': 'select alias "option text" from the drop down list with the css selector of "indentifier" if it exists'
-  'body': 'select alias "$1" from the drop down list with the css selector of "$2" if it exists'
-'select157':
-  'prefix': 'select alias "option text" from the drop down list with the css selector of alias "indentifier" if it exists'
-  'body': 'select alias "$1" from the drop down list with the css selector of alias "$2" if it exists'
+  'select101':
+    'prefix': 'select "option text" from the drop down list found by "identifier"'
+    'body': 'select "$1" from the drop down list found by "$2"'
+  'select102':
+    'prefix': 'select "option text" from the drop down list found by alias "identifier"'
+    'body': 'select "$1" from the drop down list found by alias "$2"'
+  'select103':
+    'prefix': 'select "option text" from the drop down list found by "identifier" if it exists'
+    'body': 'select "$1" from the drop down list found by "$2" if it exists'
+  'select104':
+    'prefix': 'select "option text" from the drop down list found by alias "identifier" if it exists'
+    'body': 'select "$1" from the drop down list found by alias "$2" if it exists'
+  'select105':
+    'prefix': 'select alias "option text" from the drop down list found by "identifier"'
+    'body': 'select alias "$1" from the drop down list found by "$2"'
+  'select106':
+    'prefix': 'select alias "option text" from the drop down list found by alias "identifier"'
+    'body': 'select alias "$1" from the drop down list found by alias "$2"'
+  'select107':
+    'prefix': 'select alias "option text" from the drop down list found by "identifier" if it exists'
+    'body': 'select alias "$1" from the drop down list found by "$2" if it exists'
+  'select108':
+    'prefix': 'select alias "option text" from the drop down list found by alias "identifier" if it exists'
+    'body': 'select alias "$1" from the drop down list found by alias "$2" if it exists'
+
+  'select110':
+    'prefix': 'select "option text" from the drop down list with the ID of "indentifier"'
+    'body': 'select "$1" from the drop down list with the ID of "$2"'
+  'select111':
+    'prefix': 'select "option text" from the drop down list with the ID of alias "indentifier"'
+    'body': 'select "$1" from the drop down list with the ID of alias "$2"'
+  'select112':
+    'prefix': 'select "option text" from the drop down list with the ID of "indentifier" if it exists'
+    'body': 'select "$1" from the drop down list with the ID of "$2" if it exists'
+  'select113':
+    'prefix': 'select "option text" from the drop down list with the ID of alias "indentifier" if it exists'
+    'body': 'select "$1" from the drop down list with the ID of alias "$2" if it exists'
+  'select114':
+    'prefix': 'select alias "option text" from the drop down list with the ID of "indentifier"'
+    'body': 'select alias "$1" from the drop down list with the ID of "$2"'
+  'select115':
+    'prefix': 'select alias "option text" from the drop down list with the ID of alias "indentifier"'
+    'body': 'select alias "$1" from the drop down list with the ID of alias "$2"'
+  'select116':
+    'prefix': 'select alias "option text" from the drop down list with the ID of "indentifier" if it exists'
+    'body': 'select alias "$1" from the drop down list with the ID of "$2" if it exists'
+  'select117':
+    'prefix': 'select alias "option text" from the drop down list with the ID of alias "indentifier" if it exists'
+    'body': 'select alias "$1" from the drop down list with the ID of alias "$2" if it exists'
+
+  'select120':
+    'prefix': 'select "option text" from the drop down list with the class of "indentifier"'
+    'body': 'select "$1" from the drop down list with the class of "$2"'
+  'select121':
+    'prefix': 'select "option text" from the drop down list with the class of alias "indentifier"'
+    'body': 'select "$1" from the drop down list with the class of alias "$2"'
+  'select122':
+    'prefix': 'select "option text" from the drop down list with the class of "indentifier" if it exists'
+    'body': 'select "$1" from the drop down list with the class of "$2" if it exists'
+  'select123':
+    'prefix': 'select "option text" from the drop down list with the class of alias "indentifier" if it exists'
+    'body': 'select "$1" from the drop down list with the class of alias "$2" if it exists'
+  'select124':
+    'prefix': 'select alias "option text" from the drop down list with the class of "indentifier"'
+    'body': 'select alias "$1" from the drop down list with the class of "$2"'
+  'select125':
+    'prefix': 'select alias "option text" from the drop down list with the class of alias "indentifier"'
+    'body': 'select alias "$1" from the drop down list with the class of alias "$2"'
+  'select126':
+    'prefix': 'select alias "option text" from the drop down list with the class of "indentifier" if it exists'
+    'body': 'select alias "$1" from the drop down list with the class of "$2" if it exists'
+  'select127':
+    'prefix': 'select alias "option text" from the drop down list with the class of alias "indentifier" if it exists'
+    'body': 'select alias "$1" from the drop down list with the class of alias "$2" if it exists'
+
+  'select130':
+    'prefix': 'select "option text" from the drop down list with the xpath of "indentifier"'
+    'body': 'select "$1" from the drop down list with the xpath of "$2"'
+  'select131':
+    'prefix': 'select "option text" from the drop down list with the xpath of alias "indentifier"'
+    'body': 'select "$1" from the drop down list with the xpath of alias "$2"'
+  'select132':
+    'prefix': 'select "option text" from the drop down list with the xpath of "indentifier" if it exists'
+    'body': 'select "$1" from the drop down list with the xpath of "$2" if it exists'
+  'select133':
+    'prefix': 'select "option text" from the drop down list with the xpath of alias "indentifier" if it exists'
+    'body': 'select "$1" from the drop down list with the xpath of alias "$2" if it exists'
+  'select134':
+    'prefix': 'select alias "option text" from the drop down list with the xpath of "indentifier"'
+    'body': 'select alias "$1" from the drop down list with the xpath of "$2"'
+  'select135':
+    'prefix': 'select alias "option text" from the drop down list with the xpath of alias "indentifier"'
+    'body': 'select alias "$1" from the drop down list with the xpath of alias "$2"'
+  'select136':
+    'prefix': 'select alias "option text" from the drop down list with the xpath of "indentifier" if it exists'
+    'body': 'select alias "$1" from the drop down list with the xpath of "$2" if it exists'
+  'select137':
+    'prefix': 'select alias "option text" from the drop down list with the xpath of alias "indentifier" if it exists'
+    'body': 'select alias "$1" from the drop down list with the xpath of alias "$2" if it exists'
+
+  'select140':
+    'prefix': 'select "option text" from the drop down list with the name of "indentifier"'
+    'body': 'select "$1" from the drop down list with the name of "$2"'
+  'select141':
+    'prefix': 'select "option text" from the drop down list with the name of alias "indentifier"'
+    'body': 'select "$1" from the drop down list with the name of alias "$2"'
+  'select142':
+    'prefix': 'select "option text" from the drop down list with the name of "indentifier" if it exists'
+    'body': 'select "$1" from the drop down list with the name of "$2" if it exists'
+  'select143':
+    'prefix': 'select "option text" from the drop down list with the name of alias "indentifier" if it exists'
+    'body': 'select "$1" from the drop down list with the name of alias "$2" if it exists'
+  'select144':
+    'prefix': 'select alias "option text" from the drop down list with the name of "indentifier"'
+    'body': 'select alias "$1" from the drop down list with the name of "$2"'
+  'select145':
+    'prefix': 'select alias "option text" from the drop down list with the name of alias "indentifier"'
+    'body': 'select alias "$1" from the drop down list with the name of alias "$2"'
+  'select146':
+    'prefix': 'select alias "option text" from the drop down list with the name of "indentifier" if it exists'
+    'body': 'select alias "$1" from the drop down list with the name of "$2" if it exists'
+  'select147':
+    'prefix': 'select alias "option text" from the drop down list with the name of alias "indentifier" if it exists'
+    'body': 'select alias "$1" from the drop down list with the name of alias "$2" if it exists'
+
+  'select150':
+    'prefix': 'select "option text" from the drop down list with the css selector of "indentifier"'
+    'body': 'select "$1" from the drop down list with the name of "$2"'
+  'select151':
+    'prefix': 'select "option text" from the drop down list with the css selector of alias "indentifier"'
+    'body': 'select "$1" from the drop down list with the name of alias "$2"'
+  'select152':
+    'prefix': 'select "option text" from the drop down list with the css selector of "indentifier" if it exists'
+    'body': 'select "$1" from the drop down list with the name of "$2" if it exists'
+  'select153':
+    'prefix': 'select "option text" from the drop down list with the css selector of alias "indentifier" if it exists'
+    'body': 'select "$1" from the drop down list with the css selector of alias "$2" if it exists'
+  'select154':
+    'prefix': 'select alias "option text" from the drop down list with the css selector of "indentifier"'
+    'body': 'select alias "$1" from the drop down list with the css selector of "$2"'
+  'select155':
+    'prefix': 'select alias "option text" from the drop down list with the css selector of alias "indentifier"'
+    'body': 'select alias "$1" from the drop down list with the css selector of alias "$2"'
+  'select156':
+    'prefix': 'select alias "option text" from the drop down list with the css selector of "indentifier" if it exists'
+    'body': 'select alias "$1" from the drop down list with the css selector of "$2" if it exists'
+  'select157':
+    'prefix': 'select alias "option text" from the drop down list with the css selector of alias "indentifier" if it exists'
+    'body': 'select alias "$1" from the drop down list with the css selector of alias "$2" if it exists'


remove font weight bold snippet

remove font weight bold snippet

--- snippets/styles.cson
+++ snippets/styles.cson
@@ -6,6 +6,3 @@
         $2
       }
     """
-  'Font weight bold':
-    'prefix': 'bold'
-    'body': 'font-weight: bold;'


Adding more snippets

Adding more snippets

--- snippets/verify.cson
+++ snippets/verify.cson
@@ -151,3 +151,28 @@
   'verify57':
     'prefix': 'verify that the element with the css selector of alias "identifier" should have a class of alias "class" if it exists'
     'body': 'verify that the element with the css selector of alias "$1" should have a class of alias "$2" if it exists'
+
+  'verify60':
+    'prefix': 'verify that the alias "alias" is empty'
+    'body': 'verify that the alias "$1" is empty'
+  'verify61':
+    'prefix': 'verify that the alias "alias" is not empty'
+    'body': 'verify that the alias "$1" is not empty'
+  'verify62':
+    'prefix': 'verify that the alias "alias" is a number'
+    'body': 'verify that the alias "$1" is a number'
+  'verify63':
+    'prefix': 'verify that the alias "alias" is not a number'
+    'body': 'verify that the alias "$1" is not a number'
+  'verify64':
+    'prefix': 'verify that the alias "alias" matches the regex "regex"'
+    'body': 'verify that the alias "$1" matches the regex "$2"'
+  'verify65':
+    'prefix': 'verify that the alias "alias" does not match the regex "regex"'
+    'body': 'verify that the alias "$1" does not match the regex "$2"'
+  'verify66':
+    'prefix': 'verify that the alias "alias" is equal to "value"'
+    'body': 'verify that the alias "$1" is equal to "$2"'
+  'verify67':
+    'prefix': 'verify that the alias "alias" is not equal to "value"'
+    'body': 'verify that the alias "$1" is not equal to "$2"'


`loading` results destroyable again but results now also know that they are destroyed

--- lib/editor/result.coffee
+++ lib/editor/result.coffee
@@ -81,7 +81,6 @@
     if loading then @setContent views.render(span 'loading icon icon-gear'), opts

   setContent: (view, {error, loading}={}) ->
-    @loading = loading
     while @view.firstChild?
       @view.removeChild @view.firstChild
     if error then @view.classList.add 'error' else @view.classList.remove 'error'
@@ -106,11 +105,11 @@
     trees.toggle $(@view).find('> .tree')

   remove: ->
-    if @loading then return
     @view.classList.add 'ink-hide'
     @timeout 200, => @destroy()

   destroy: ->
+    @isDestroyed = true
     @emitter.emit 'destroyed'
     @emitter.dispose()
     @marker.destroy()


Give the graph a name

Give the graph a name

--- src/WebSocket.coffee
+++ src/WebSocket.coffee
@@ -160,6 +160,7 @@
               payload:
                 id: 'foo'
                 main: true
+                name: 'Foo graph'
           ]
           expects2 = [
               protocol: 'graph'
@@ -189,6 +190,7 @@
             baseDir: path.resolve __dirname, '../'
             id: 'foo'
             main: true
+            name: 'Foo graph'

       describe 'adding an edge', ->
         it 'should provide the edge back', (done) ->


Escalate properly

Escalate properly

--- src/god.coffee
+++ src/god.coffee
@@ -14,13 +14,12 @@
         value = new World(rx, key, value, this)
       @doc.put(key, value)

-  get_local: (key, world) ->
+  get_local: (key) ->
     @doc.get(key)

-  get_raw: (key) ->
-    value = @get_local(key, this)
-    console.log("get_raw: #{this}:(#{key},#{value}) -> #{@up}")
-    if value? then value else @up.get_local(key, this)
+  get_raw: (key, world=this) ->
+    value = @get_local(key)
+    if (value? || !@up?) then value else @up.get_raw(key, this)

   get: (key) ->
     value = @get_raw(key)
@@ -50,7 +49,7 @@
     "World:#{@label}"

 class Root
-  get_local: (key, world) ->
+  get_raw: (key, world) ->
     console.log "#{key} not found"
     console.log world
   toString: ->


modify LIST data

modify LIST data

--- src/baby-ftpd.coffee
+++ src/baby-ftpd.coffee
@@ -80,6 +80,11 @@
       socket.reply 220

     @piServer.listen port, host
+
+  @changeCarriageCode: (data)->
+    retData = data.replace /[\n\r]/g, "\n"
+    retData = retData.replace /[\n]/g, "\r\n"
+    return retData

   # Standard messages for status. (RFC 959)
   messages =
@@ -238,7 +243,8 @@
             @dtpServer.close()
           @reply 550, "#{reqPath}: No such file or directory"
         else
-          @dtpServer.dtpSocket.sender files.join("\r\n") + "\r\n"
+          ls = BabyFtpd.changeCarriageCode files.join("\n") + "\n"
+          @dtpServer.dtpSocket.sender ls

     "LIST": (reqPath = "")->
       @fileSystem.getList @sessionDir, reqPath, (err, stdout, stderr)=>
@@ -247,7 +253,8 @@
             @dtpServer.close()
           @reply 550, "#{reqPath}: No such file or directory"
         else
-          @dtpServer.dtpSocket.sender stdout.replace(/^total [0-9]+$/im, "").trim()
+          ls = BabyFtpd.changeCarriageCode stdout.replace(/^total [0-9]+$/im, "").trim()+"\n"
+          @dtpServer.dtpSocket.sender ls

     "RETR": (reqPath)->
       @fileSystem.getFile @sessionDir, reqPath, (err, data)=>


Add support for fetch rewrites

Add support for fetch rewrites

Closes #1

--- src/ractive-adaptors-parse.coffee
+++ src/ractive-adaptors-parse.coffee
@@ -22,6 +22,10 @@
 			eventManager.emit(@className + "-" + @id, @, key)
 		return retVal

+	wrappedFetch: (target, forceFetch, options) ->
+		@_fetch.apply @, arguments
+		.then -> eventManager.emit(@className + "-" + @id, target, false)
+
 	eventListener: (ractive, prefixer, desiredTarget) -> (target, key) => if target is desiredTarget
 		ractive.set prefixer if key is false
 				@::get.apply target
@@ -30,6 +34,11 @@
 	### Wrap Parse Object ###
 	constructor: (@ractive, @object, keypath, prefixer) ->
 		object.set = @::wrappedSet
+		objectController = @Parse.CoreManager.getObjectController()
+		unless objectController.ractiveParseWrapper
+			objectController._fetch = objectController.fetch
+			objectController.fetch = @::wrappedFetch
+			objectController.ractiveParseWrapper = true

 		@listener = [
 			@object.className + "-" + @object.id,


Updating test cases

Updating test cases

--- spec/e2e.stubs.spec.coffee
+++ spec/e2e.stubs.spec.coffee
@@ -278,8 +278,8 @@
             waitsFor ( -> context.done ), 'post-fallback request to finish', 1000, ->
                assert context.response.statusCode is 200
                done()
-
-      describe 'match file as string', ->
+
+      describe 'match file as plain string', ->
         it 'should match the post body with the file properly', (done) ->
           context.url = '/file/body/matchstring'
           context.method = 'post'
@@ -298,3 +298,4 @@
             assert context.response.data.length isnt 0
             assert context.response.code is 200
             done()
+


Disable autoHeight when explicitly setting editors' height in specs

--- spec/find-view-spec.coffee
+++ spec/find-view-spec.coffee
@@ -617,6 +617,7 @@

     it "shows an icon when search wraps around and the editor scrolls", ->
       editorView.style.height = "80px"
+      editor.update?({autoHeight: false})
       atom.views.performDocumentPoll()
       expect(editor.getVisibleRowRange()).toEqual [0, 3]

@@ -645,6 +646,7 @@

     it "does not show the wrap icon when the editor does not scroll", ->
       editorView.style.height = "400px"
+      editor.update?({autoHeight: false})
       atom.views.performDocumentPoll()
       expect(editor.getVisibleRowRange()).toEqual [0, 12]

@@ -1104,42 +1106,26 @@
         findView.findEditor.focus()

       it "scrolls to the first match if the settings scrollToResultOnLiveSearch is true", ->
+        editorView.style.height = "3px"
+        editor.update?({autoHeight: false})
+        atom.views.performDocumentPoll()
+        editor.moveToTop()
         atom.config.set('find-and-replace.scrollToResultOnLiveSearch', true)
-        if editorView.logicalDisplayBuffer
-          editorView.setHeight(3)
-        else
-          editor.setHeight(3)
-        editor.moveToTop()
-        if editorView.logicalDisplayBuffer
-          originalScrollPosition = editorView.getScrollTop()
-        else
-          originalScrollPosition = editor.getScrollTop()
         findView.findEditor.setText 'Array'
         advance()
-        if editorView.logicalDisplayBuffer
-          expect(editorView.getScrollTop()).not.toEqual originalScrollPosition
-        else
-          expect(editor.getScrollTop()).not.toEqual originalScrollPosition
+        expect(editorView.getScrollTop()).toBeGreaterThan(0)
         expect(editor.getSelectedBufferRange()).toEqual [[11, 14], [11, 19]]
         expect(findView.findEditor).toHaveFocus()

       it "doesn't scroll to the first match if the settings scrollToResultOnLiveSearch is false", ->
+        editorView.style.height = "3px"
+        editor.update?({autoHeight: false})
+        atom.views.performDocumentPoll()
+        editor.moveToTop()
         atom.config.set('find-and-replace.scrollToResultOnLiveSearch', false)
-        if editorView.logicalDisplayBuffer
-          editorView.setHeight(3)
-        else
-          editor.setHeight()
-        editor.moveToTop()
-        if editorView.logicalDisplayBuffer
-          originalScrollPosition = editorView.getScrollTop()
-        else
-          originalScrollPosition = editor.getScrollTop()
         findView.findEditor.setText 'Array'
         advance()
-        if editorView.logicalDisplayBuffer
-          expect(editorView.getScrollTop()).toEqual originalScrollPosition
-        else
-          expect(editor.getScrollTop()).toEqual originalScrollPosition
+        expect(editorView.getScrollTop()).toBe(0)
         expect(editor.getSelectedBufferRange()).toEqual []
         expect(findView.findEditor).toHaveFocus()


Infrastructure for testing Sets.

Infrastructure for testing Sets.

--- spec/hashparamsSpec.coffee
+++ spec/hashparamsSpec.coffee
@@ -1,3 +1,30 @@
+#region Set helpers
+setOf = (values...) ->
+    result = new Set
+    values.forEach (value) -> result.add value
+    result
+Set.prototype.jasmineToString = ->
+    # CoffeeScript list comprehensions don't work on Set because it has
+    # a .size property, not .length. Manually arrayify.
+    values = []
+    @forEach (value) -> values.push value
+    "Set{#{values.join(', ')}}"
+beforeEach ->
+    jasmine.addCustomEqualityTester (a, b) ->
+        if (a instanceof Set && b instanceof Set)
+            if (a.size == b.size)
+                clone = new Set
+                a.forEach (value) -> clone.add value
+                b.forEach (value) -> clone.delete value
+                clone.size == 0
+            else false # suppress Jasmine's default "all Set instances are equal" behavior
+#endregion
+
+describe 'Set comparison', ->
+    it 'works', ->
+        s = setOf 5, 4
+        expect(s).toEqual setOf 4, 5
+
 describe 'HashParams', ->
     describe 'when constructed with', ->
         describe 'nothing', ->


Add failing test

Add failing test

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -1167,6 +1167,23 @@

             expect(fs.existsSync(dirPath)).toBeTruthy()
             expect(fs.existsSync(path.join(dirPath, path.basename(dirPath)))).toBeFalsy()
+
+      describe "when pasting entries which don't exist anymore", ->
+        it "skips the entry which doesn't exist", ->
+          filePathDoesntExist1 = path.join(dirPath2, "test-file-doesnt-exist1.txt")
+          filePathDoesntExist2 = path.join(dirPath2, "test-file-doesnt-exist2.txt")
+
+          LocalStorage['tree-view:copyPath'] = JSON.stringify([filePath2, filePathDoesntExist1, filePath3, filePathDoesntExist2])
+
+          fileView.click()
+          atom.commands.dispatch(treeView.element, "tree-view:paste")
+
+          expect(fs.existsSync(path.join(dirPath, path.basename(filePath2)))).toBeTruthy()
+          expect(fs.existsSync(path.join(dirPath, path.basename(filePath3)))).toBeTruthy()
+          expect(fs.existsSync(path.join(dirPath, path.basename(filePathDoesntExist1)))).toBeFalsy()
+          expect(fs.existsSync(path.join(dirPath, path.basename(filePathDoesntExist2)))).toBeFalsy()
+          expect(fs.existsSync(filePath2)).toBeTruthy()
+          expect(fs.existsSync(filePath3)).toBeTruthy()

       describe "when a file has been copied", ->
         describe "when a file is selected", ->


Atom: Add command to insert now as timestamp

--- home/atom/init.coffee
+++ home/atom/init.coffee
@@ -12,3 +12,13 @@
 #   editor = editorView.getEditor()
 #   if path.extname(editor.getPath()) is '.md'
 #     editor.setSoftWrap(true)
+
+
+atom.commands.add 'atom-text-editor', 'custom:now', ->
+  if editor = atom.workspace.getActiveTextEditor()
+    now = new Date()
+        .toISOString()
+        .replace(/.\d{3}Z$/, '')
+        .replace('T', ' ')
+
+    editor.insertText(now)


ineffectual obstacles

ineffectual obstacles

--- obot.coffee
+++ obot.coffee
@@ -50,7 +50,7 @@

 # level graphics
 cell_size = 32
-renderLevel = (level_ctx, prog_ctx) ->
+renderLevel = (level_ctx, prog_ctx, t) ->
   ctx = level_ctx
   ctx.save()

@@ -99,6 +99,14 @@
   ctx.stroke()
   ctx.restore()

+  # obstacles
+  show_frame = @frame
+  frame_t = 0
+  if @animation_mode == levelAnimMode.MOVING
+    frame_t = (t - @move_control.start.t) * bot_speed
+    show_frame = @frame - 1
+  for o in @obstacles
+    o.render(ctx, show_frame, frame_t, t)

   # bot
   @bot.render(ctx)
@@ -168,6 +176,31 @@

   return

+# obstacle graphics
+renderMine = (ctx) ->
+    ctx.fillStyle = 'black'
+    ctx.strokeStyle = 'white'
+    ctx.lineWidth = 1
+
+    ctx.beginPath()
+    ctx.moveTo(-cell_size*.35,-cell_size*.35)
+    ctx.lineTo(cell_size*.35,cell_size*.35)
+    ctx.moveTo(cell_size*.35,-cell_size*.35)
+    ctx.lineTo(-cell_size*.35,cell_size*.35)
+
+    ctx.moveTo(0,-cell_size*.4)
+    ctx.lineTo(0,cell_size*.4)
+    ctx.moveTo(cell_size*.4,0)
+    ctx.lineTo(-cell_size*.4,0)
+    ctx.stroke()
+
+    ctx.beginPath()
+    ctx.arc(0,0, cell_size*.3, 0, Math.PI*2)
+    ctx.fill()
+    ctx.stroke()
+
+    return
+
 # general directions
 UP = (theta: Math.PI/2, dx: 0, dy: -1)
 LEFT = (theta: Math.PI, dx: -1, dy: 0)
@@ -455,7 +488,43 @@
   render: renderUndoButton
   action: undoButtonAction

-# level
+# obstacles
+class Static_obstacle
+  constructor: (@xi, @yi) ->
+
+  render: (ctx, frame, frame_t, t) ->
+    ctx.save()
+    ctx.translate((@xi + .5)*cell_size+.5, (@yi + .5)*cell_size+.5)
+
+    renderMine(ctx)
+    ctx.restore()
+    return
+
+  isHit: (xi, yi, frame) ->
+    return xi == @xi and yi == @yi
+
+class Mobile_obstacle
+  constructor: (@xi, @yi, @dxi, @dyi) ->
+
+  render: (ctx, frame, frame_t, t) ->
+    distx = (frame + frame_t) * @dxi
+    disty = (frame + frame_t) * @dyi
+
+    ctx.save()
+
+    ctx.translate((@xi + .5 + distx)*cell_size+.5,
+                  (@yi + .5 + disty)*cell_size+.5)
+
+    renderMine(ctx)
+    ctx.restore()
+    return
+
+  isHit: (xi, yi, frame) ->
+    # NOTE: if the obstacle is to move more than one cell per frame in some
+    # direction, this check will need to be expanded
+    return xi == @xi+frame*@dxi and yi == @yi+frame*@dyi
+
+# level logic

 levelMode =
   STOP: 1
@@ -476,17 +545,19 @@

   @show_current_command = 0
   @next_command = 0
+  @frame = 0

   @mode = levelMode.STOP
+
+  @animation_mode = levelAnimMode.READY
+  delete @move_control
+  @last_t = Date.now()/1000

   return

 resetAndRunLevel = ->
   @reset()
   @mode = levelMode.PLAYFWD
-  @last_t = Date.now()/1000
-  @animation_mode = levelAnimMode.READY
-  delete @move_control

   return

@@ -543,6 +614,8 @@
         @move_control = next_move_control
         @animation_mode = levelAnimMode.MOVING
         @show_current_command = step_command
+      else
+        # TODO: Need to play a death animation and restart

   # immediately run this if we switched to MOVING above
   if @animation_mode == levelAnimMode.MOVING
@@ -559,24 +632,31 @@
   return

 stepLevelSimulation = ->
-  # TODO: detect illegal moves (obstacles), show error
-  if @mode == levelMode.TURNAROUND
-    # NOTE: even TURNAROUND might fail if something hits you
-    return true
-
   # bot gets item when leaving its cell
   if @bot.xi == @item_location.xi and @bot.yi == @item_location.yi
     @bot.has_item = true

-  @commands[@next_command].action(this)
+  if @mode isnt levelMode.TURNAROUND # @next_command is illegal for TURNAROUND
+    @commands[@next_command].action(this)
+
+  @frame++
+
+  # check if this move takes us into an obstacle
+  for o in @obstacles
+    if o.isHit(@bot.xi, @bot.yi, @frame)
+      console.log("hit obstacle at frame " + @frame)
+      # TODO: error display, take back move (I think that goes elsewhere)
+      #return false
+
   @advanceNextCommand()

   return true

-setupLevel = (initial_bot_location, item_location) ->
+setupLevel = (initial_bot_location, item_location, obstacles) ->
   o =
     initial_bot_location: initial_bot_location
     item_location: item_location
+    obstacles: obstacles
     bot: (render: renderBot)
     commands: [ ]
     next_command: 0
@@ -605,7 +685,11 @@
   click: clickControlPanel

 control_panel = setupControlPanel()
-level_state = setupLevel((xi:0, yi:5, dir:0), (xi:6, yi:5))
+level_state = setupLevel(
+  (xi:0, yi:5, dir:0), (xi:6, yi:5),
+  [new Static_obstacle(1,5),
+   new Mobile_obstacle(2,1,0,1)]
+  )

 stop = false

@@ -616,7 +700,7 @@
   clear(prog_cnv, prog_ctx)

   level_state.animate(t)
-  level_state.render(level_ctx, prog_ctx)
+  level_state.render(level_ctx, prog_ctx, t)

   #stop = true


add an alphabetical sort for proposals

add an alphabetical sort for proposals

--- @client/filter.coffee
+++ @client/filter.coffee
@@ -67,10 +67,16 @@
   sort = fetch 'sort_proposals'
   set_sort() if !sort.func?

-  proposal_rank = sort.func
-
-  proposals = proposals.slice().sort (a,b) ->
-    return proposal_rank(b, sort.opinion_value) - proposal_rank(a, sort.opinion_value)
+
+  if sort.comp
+    comp = sort.comp
+    proposals = proposals.slice().sort (a,b) ->
+      return comp(a,b)
+
+  else
+    proposal_rank = sort.func
+    proposals = proposals.slice().sort (a,b) ->
+      return proposal_rank(b, sort.opinion_value) - proposal_rank(a, sort.opinion_value)

   # filter out filtered proposals
   filter_out = fetch 'filtered'
@@ -113,6 +119,11 @@

 sort_options = [
+  {
+    name: 'Alphabetically'
+    comp: (a, b) -> a.name.localeCompare b.name
+    description: "Sort alphabetically by the proposal's title"
+  },
   {
     func: (proposal, opinion_value) -> basic_proposal_scoring(proposal, opinion_value).avg
     name: 'Average Score'


coffeescript strings

coffeescript strings

--- src/hubot/xmpp.coffee
+++ src/hubot/xmpp.coffee
@@ -131,7 +131,7 @@
     for str in strings
       console.log "Sending to #{user.room}: #{str}"

-      to = if user.type in ['direct', 'chat'] then user.room + '/' + user.id else user.room
+      to = if user.type in ['direct', 'chat'] then "#{user.room}/#{user.id}" else user.room

       message = new Xmpp.Element('message',
                   from: @options.username


Use anchors instead of buttons on install panel

Buttons retain focus after click giving them low contrast.

--- lib/install-panel.coffee
+++ lib/install-panel.coffee
@@ -26,8 +26,8 @@
             @div class: 'editor-container', =>
               @subview 'searchEditorView', new TextEditorView(mini: true)
             @div class: 'btn-group', =>
-              @button outlet: 'searchPackagesButton', type: 'button', class: 'btn btn-default selected', 'Packages'
-              @button outlet: 'searchThemesButton', type: 'button', class: 'btn btn-default', 'Themes'
+              @a outlet: 'searchPackagesButton', class: 'btn btn-default selected', 'Packages'
+              @a outlet: 'searchThemesButton', class: 'btn btn-default', 'Themes'

           @div outlet: 'searchErrors'
           @div outlet: 'searchMessage', class: 'alert alert-info search-message icon icon-search'


simplified macro that generates the REPL greeting

--- src/apl.coffee
+++ src/apl.coffee
@@ -75,8 +75,7 @@
         b.slice 0, k
       ).toString 'utf8'
     else
-      macro greeting -> macro.valToNode "ngn apl #{(new Date).toISOString().replace /T.*/, ''}\n"
-      process.stdout.write greeting()
+      process.stdout.write macro -> macro.valToNode "ngn apl #{(new Date).toISOString().replace /T.*/, ''}\n"
       rl = require('readline').createInterface process.stdin, process.stdout
       rl.setPrompt '      '
       ctx = Object.create vocabulary


add colorpicker and custom toggle to app.js

--- src/app.coffee
+++ src/app.coffee
@@ -1,15 +1,22 @@
 opacity_input = document.getElementById 'opacity'
 on_off_toggle = document.getElementById 'on-off-toggle'
+custom_toggle = document.getElementById 'custom_toggle'
+colorpicker = document.getElementById 'colorpicker'

-chrome.storage.local.get ['on', 'opacity'], (items) ->
+chrome.storage.local.get ['on', 'opacity', 'custom', 'custom-color'], (items) ->
     opacity_input.value = items['opacity']
-    if items['on']
-        on_off_toggle.checked = true
-    else
-        on_off_toggle.checked = false
+    on_off_toggle.checked = items['on']
+    custom_toggle.checked = items['custom']
+    colorpicker = items['custom-color']

 opacity_input.addEventListener 'input', (event) ->
     chrome.storage.local.set 'opacity': opacity_input.value, ->

 on_off_toggle.addEventListener 'click', (event) ->
     chrome.storage.local.set 'on' : on_off_toggle.checked, ->
+
+custom_toggle.addEventListener 'click', (event) ->
+    chrome.storage.local.set 'on' : custom_toggle.checked, ->
+
+colorpicker.addEventListener 'click', (event) ->
+    chrome.storage.local.set 'on' : colorpicker.value, ->


fix drag error

fix drag error

--- src/app.coffee
+++ src/app.coffee
@@ -724,7 +724,7 @@
   return false

-performDrag = (callbacks) ->
+performDrag = (event, callbacks) ->
   start_drag_x = event.pageX
   start_drag_y = event.pageY

@@ -747,6 +747,8 @@
     $("#playlist-track-#{result.track_id}").addClass "border-#{result.direction}"

   onDragEnd = (event) ->
+    return false unless event.button is 0
+
     if started_drag
       callbacks.complete getDragPosition(event.pageX, event.pageY), event
     else
@@ -816,7 +818,7 @@

       # dragging
       if not skip_drag
-        performDrag
+        performDrag event,
           complete: (result, event) ->
             delta =
               top: 0
@@ -933,7 +935,7 @@

       # dragging
       if not skip_drag
-        performDrag
+        performDrag event,
           complete: (result, event) ->
             delta =
               top: 0


support no-player-info output

support no-player-info output

--- src/app.coffee
+++ src/app.coffee
@@ -49,7 +49,10 @@

         , (callback) ->

-            AccountInfo.fetch callback
+            if not argv.noplayerinfo
+                AccountInfo.fetch callback
+            else
+                callback()

         , (callback) ->


Fix documentation.

Fix documentation.

--- src/app.coffee
+++ src/app.coffee
@@ -91,7 +91,7 @@

         addService: (service) ->
             if @services.isEmpty()
-                # Remove possible services
+                # Remove possible units
                 # that had been added through
                 # other means than service
                 # selection.


Adds a Clippy meme to the meme generator, which
listens for "it looks like you('re) <text>"

```
Hubot> hubot it looks like you're trying to use Hubot
Hubot> http://images.memegenerator.net/instances/33587375.jpg
```

(Thanks to @jasonkarns for the suggestion)

--- src/scripts/meme_generator.coffee
+++ src/scripts/meme_generator.coffee
@@ -26,6 +26,7 @@
 #   hubot (Oh|You) <text> (Please|Tell) <text> - Willy Wonka
 #   hubot <text> you're gonna have a bad time - Bad Time Ski Instructor
 #   hubot one does not simply <text> - Lord of the Rings Boromir
+#   hubot it looks like (you|you're) <text> - Generates Clippy
 #
 # Author:
 #   skalnik
@@ -102,6 +103,11 @@
         regex: /(grumpy cat) (.*),(.*)/i,
         generatorID: 1590955,
         imageID: 6541210
+      },
+      {
+        regex: /(it looks like you're|it looks like you) (.*)/i,
+        generatorID: 20469,
+        imageID: 1159769
       }
     ]


R~bg

R~bg

--- AppStoreClient.coffee
+++ AppStoreClient.coffee
@@ -154,9 +154,10 @@
     country = obj?.country ? require("AppStoreClient/AppStoreClient").getTzOff()
     limit = obj?.limit ? 200
     url = "https://itunes.apple.com/"+country.toLowerCase()+"/rss/topsongs/limit=#{limit}/explicit=true/xml"
-    unless ENV_PRODUCTION then Ti.API.debug "url=#{url}"
-
-    Titanium.Yahoo.yql 'select * from xml where url="'+url+'"', (e) ->
+
+    mojiretu = 'select * from xml where url="'+url+'"'
+    unless ENV_PRODUCTION then Ti.API.debug "url=#{url} moji=#{mojiretu}"
+    Ti.Yahoo.yql mojiretu, (e) ->
       if e.success and e.data?
         arr = e.data?.feed?.entry ? []
         objs = for item in arr


Fix HTTP.Client when request failed and no hosts given

--- src/marbles/http/client.js.coffee
+++ src/marbles/http/client.js.coffee
@@ -35,7 +35,7 @@
         callback?.success?(res, xhr)
         @options.success?(res, xhr)
       else
-        if hosts.length && @options.cycle
+        if hosts?.length && @options.cycle
           return @request(method, path, params, callback, hosts)
         callback?.error?(res, xhr)
         @options.error?(res, xhr)


Spread in other props consumer might want to pass it

--- src/index.cjsx
+++ src/index.cjsx
@@ -63,7 +63,9 @@
         className={"react-gravatar " + @props.className}
         src={retinaSrc}
         height={@props.size}
-        width={@props.size} />
+        width={@props.size}
+        {...@props}
+      />
     else
       <img
         style={@props.style}
@@ -71,4 +73,6 @@
         src={src}
         srcSet={retinaSrc + " 2x"}
         height={@props.size}
-        width={@props.size} />
+        width={@props.size}
+        {...@props}
+      />


Fixes locking issue

Fixes locking issue

--- src/bootstrap.coffee
+++ src/bootstrap.coffee
@@ -22,10 +22,16 @@
 currentFlavour = null

 createLock = ()->
+  _open = false
   _cbs = []
   run: (cb)->
-    _cbs.push cb
+    if _open
+      cb()
+    else
+      _cbs.push cb
+
   unlock: ()->
+    _open = true
     cb() for cb in _cbs

 # Helpers to manage the calls to functions defined from the start


Removeing jQuery completely

Removeing jQuery completely

--- src/chocolate.coffee
+++ src/chocolate.coffee
@@ -24,8 +24,7 @@
       throw "You don't have defaultOptions or templates variables"
       return false

-    # jQuery
-    @options = $.extend defaultOptions, options
+    @options = merge defaultOptions, options

     ###
      Подготовка шаблонов
@@ -132,9 +131,7 @@

     @_hideLess() if @length is 1

-    addHandler window, 'resize', =>
-      image = @images[@current]
-      @updateDimensions image.width, image.height
+    addHandler window, 'resize', @_onResize

     @open cid
     @
@@ -145,9 +142,9 @@

       if @options.thumbnails
         @thumbnails.innerHTML = ''
-        removeHandler @overlay, 'click', '.choc-thumbnails-toggle'
-
-      removeHandler window, 'resize'
+        removeHandler @overlay, 'click', @_toggleThumbnails, '.choco-thumbnails-toggle'
+
+      removeHandler window, 'resize', @_onResize

       @current = null

@@ -325,22 +322,7 @@
       addHandler thumbnail, 'click', -> _this.open toInt @getAttribute 'data-cid'
       setStyle thumbnail, "background-image": "url(#{image})"

-    addHandler @overlay, 'click', ->
-      current = _this.images[_this.current]
-      status  = hasClass _this.thumbnails, 'choco-hide'
-
-      if isStorage
-        localStorage.setItem 'choco-thumbnails', if status then 1 else 0
-
-      if status
-        removeClass _this.thumbnails, 'choco-hide'
-        removeClass this, 'choco-hide'
-      else
-        addClass _this.thumbnails, 'choco-hide'
-        addClass this, 'choco-hide'
-
-      _this.updateDimensions current.width, current.height if current
-    , '.choco-thumbnails-toggle'
+    addHandler @overlay, 'click', @_toggleThumbnails, '.choco-thumbnails-toggle'

     if isStorage and not hasClass @thumbnails, 'choco-hide'
       status = localStorage.getItem('choco-thumbnails') or 1
@@ -492,6 +474,27 @@
     @dimensions.thumbnails = false if @options.thumbnails
     addClass @overlay, 'choco-hideless'
     @
+
+  _onResize: (event) =>
+    image = @images[@current]
+    @updateDimensions image.width, image.height
+
+  _toggleThumbnails: (event) =>
+    current = @images[@current]
+    status  = hasClass @thumbnails, 'choco-hide'
+
+    if isStorage
+      localStorage.setItem 'choco-thumbnails', if status then 1 else 0
+
+    if status
+      removeClass @thumbnails, 'choco-hide'
+      removeClass event.target, 'choco-hide'
+    else
+      addClass @thumbnails, 'choco-hide'
+      addClass event.target, 'choco-hide'
+
+    @updateDimensions current.width, current.height if current
+

   ###
    Private method
@@ -514,6 +517,13 @@
     width += parseFloat css style for style in styles
     width

+merge = (o1, o2) ->
+  for own key, value of o2
+    if value instanceof Object
+      o1[key] = merge o1[key], value
+    else
+      o1[key] = value
+  o1

 getTarget = (element, selector) ->
   if selector?
@@ -530,17 +540,13 @@
     for ev in event
       target.addEventListener ev, listener, false

-removeHandler = (element, event, selector) ->
+removeHandler = (element, event, listener, selector) ->
   target = getTarget element, selector
-  ###
   if target?
-    listeners = getEventListeners target
     unless event instanceof Array
       event = [event]
     for ev in event
-      for listener in listeners[ev]
-        target.removeEventListener ev, listener, false
-  ###
+      target.removeEventListener ev, listener, false

 hasClassList = (element, selector) ->
   target = getTarget element, selector


Assume events_grammar is present for all plugins

--- lib/api_methods.coffee
+++ lib/api_methods.coffee
@@ -16,10 +16,7 @@
   do ->
     for key in _.keys(config.apps)
       continue if key == "www"
-      try
-        grammar_getters[key] = require("../plugins/#{key}/lib/events_grammar")
-      catch e
-        continue
+      grammar_getters[key] = require("../plugins/#{key}/lib/events_grammar")

   # Private
   get_user = (query, callback) ->


strip ignored before anything else; quick fix for #2

--- src/importless.coffee
+++ src/importless.coffee
@@ -28,9 +28,9 @@
 exports.detectDependencies = (directories=['./']) ->
   if typeof directories is 'string' then directories = [directories]
   files = (files or []).concat(@findAll dir) for dir in directories
+  files = @stripIgnored files, @getIgnored()
   files = @cssToLess files
   files = @stripImported files, @getImported(files)
-  files = @stripIgnored files, @getIgnored()

 # return both less and css files from directory
 exports.findAll = (directory) ->


Remove deprecated InPort process callback

--- src/lib/InPort.coffee
+++ src/lib/InPort.coffee
@@ -8,13 +8,7 @@
 platform = require './Platform'

 class InPort extends BasePort
-  constructor: (options, process) ->
-    @process = null
-
-    if not process and typeof options is 'function'
-      process = options
-      options = {}
-
+  constructor: (options) ->
     options ?= {}

     options.buffered ?= false
@@ -22,15 +16,9 @@
     options.scoped ?= true
     options.triggering ?= true

-    if not process and options and options.process
-      process = options.process
+    if options.process
+      platform.deprecated 'InPort process callback is deprecated. Please use Process API'
       delete options.process
-
-    if process
-      platform.deprecated 'InPort process callback is deprecated. Please use Process API or the InPort handle option'
-      unless typeof process is 'function'
-        throw new Error 'process must be a function'
-      @process = process

     if options.handle
       platform.deprecated 'InPort handle callback is deprecated. Please use Process API'
@@ -67,7 +55,6 @@
       @handleIP ip, localId

   handleIP: (ip, id) ->
-    return if @process
     return if @options.control and ip.type isnt 'data'
     ip.owner = @nodeInstance
     ip.index = id if @isAddressable()
@@ -97,18 +84,10 @@

       # Notify receiver
       if @isAddressable()
-        @process event, id, @nodeInstance if @process
         @emit event, id
       else
-        @process event, @nodeInstance if @process
         @emit event
       return
-
-    if @process
-      if @isAddressable()
-        @process event, payload, id, @nodeInstance
-      else
-        @process event, payload, @nodeInstance

     # Emit port event
     return @emit event, payload, id if @isAddressable()


Improving loop performance

Improving loop performance

--- src/lib/InPort.coffee
+++ src/lib/InPort.coffee
@@ -39,9 +39,8 @@

     @prepareBuffer()

+  # Assign a delegate for retrieving data should this inPort
   attachSocket: (socket, localId = null) ->
-
-    # Assign a delegate for retrieving data should this inPort
     # have a default value.
     if @hasDefault()
       if @handle


Clear database on start

Clear database on start

--- lib/collections.coffee
+++ lib/collections.coffee
@@ -1,5 +1,14 @@
-# Model
+# Collections

+@Players   = new Meteor.Collection 'players'
+@Games     = new Meteor.Collection 'games'
 @Questions = new Meteor.Collection 'questions'
-@Games     = new Meteor.Collection 'games'
-@Players   = new Meteor.Collection 'players'
+@Sounds    = new Meteor.Collection 'sounds'
+
+Meteor.startup ->
+  # TODO: Only for development
+  console.log "Clearing db.."
+  @Players.remove({})
+  @Games.remove({})
+  @Questions.remove({})
+  @Sounds.remove({})


notify via growl on error

notify via growl on error

--- src/lib/docpad.coffee
+++ src/lib/docpad.coffee
@@ -1395,6 +1395,7 @@
 		err = new Error(err)  unless err.message?
 		err.logged = true
 		docpad.log(type, locale.errorOccured, err.message, err.stack)
+		docpad.notify err.message, title: locale.errorOccured

 		# Report the error back to DocPad using airbrake
 		if docpad.config.reportErrors and airbrake


Fix small typo

Fix small typo

--- lib/linter-flake8.coffee
+++ lib/linter-flake8.coffee
@@ -80,7 +80,7 @@
       if tmp_file_names.length > 0
         @configFile = findFile @cwd, tmp_file_names
     else
-      console.log 'no udpate needed, equal.'
+      console.log 'no update needed, equal.'

   updateCommand: ->
     binary_name = atom.config.get 'linter-flake8.binaryName'


compute-client: cache wasn't properly implemented!  :-(

--- src/smc-hub/compute-client.coffee
+++ src/smc-hub/compute-client.coffee
@@ -98,7 +98,7 @@
     if compute_server_cache?
         opts.cb(undefined, compute_server_cache)
     else
-        new ComputeServerClient(opts)
+        compute_server_cache = new ComputeServerClient(opts)

 class ComputeServerClient
     constructor: (opts) ->


reconnect to compute server if we get "error writing to socket"; https://github.com/sagemathinc/smc/issues/507

--- src/smc-hub/compute-client.coffee
+++ src/smc-hub/compute-client.coffee
@@ -1007,10 +1007,17 @@
             cb      : (err, resp) =>
                 if err
                     dbg("error calling compute server -- #{err}")
-                    # For heavily loaded systems, an error as above can happen a lot.
+                    # CRITICAL: For heavily loaded systems, an error as above can happen a lot.
                     # The server will get removed when the connection itself closes.
-                    # So do not remove from cache like I hade here!!
-                    ## NO -- @compute_server.remove_from_cache(host:@host)
+                    # So do not remove from cache except in some cases (not for every
+                    # possible err message).  Removing from the cache willy nilly in all cases
+                    # results in a huge number of connections from the hub to compute
+                    # servers, which crashes everything.
+                    if err.indexOf('error writing to socket') != -1
+                        # See https://github.com/sagemathinc/smc/issues/507
+                        # Experience suggests that when we get this error, it gets stuck like this
+                        # and never goes away -- in this case we want to try again with a new connection.
+                        @compute_server.remove_from_cache(host:@host)
                     opts.cb(err)
                 else
                     dbg("got response #{misc.to_safe_str(resp)}")


add code to set all quotas of *all* projects on the given host.

--- src/smc-hub/compute-client.coffee
+++ src/smc-hub/compute-client.coffee
@@ -741,6 +741,39 @@
                 async.mapLimit(target, opts.limit, f, cb)
         ], opts.cb)

+    # Set all quotas of *all* projects on the given host.
+    # Do this periodically as part of general maintenance in case something slips through the cracks.
+    set_all_quotas: (opts) =>
+        opts = defaults opts,
+            host  : required
+            limit : 1   # number to do at once
+            cb    : undefined
+        dbg = @dbg("set_all_quotas")
+        dbg("host=#{opts.host}, limit=#{opts.limit}")
+        projects = undefined
+        async.series([
+            (cb) =>
+                dbg("get all the projects on this server")
+                @database.get_projects_on_compute_server
+                    compute_server : opts.host
+                    cb             : (err, x) =>
+                        projects = x
+                        cb(err)
+            (cb) =>
+                dbg("call set_all_quotas on each project")
+                n = 0
+                f = (project, cb) =>
+                    n += 1
+                    dbg("#{n}/#{projects.length}")
+                    @project
+                        project_id : project.project_id
+                        cb         : (err, p) =>
+                            if err
+                                cb(err)
+                            else
+                                p.set_all_quotas(cb: cb)
+                async.mapLimit(projects, opts.limit, f, cb)
+            ])

 class ProjectClient extends EventEmitter


make the memorize path so light I might not notice it.

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -682,7 +682,7 @@

     style: ->
         if @props.history
-            return {color: '#a0a0a0'}
+            return {color: '#c0c0c0'}
         else if @props.active
             return {color: COLORS.BS_BLUE_BGRND}
         {}


make fixes

make fixes

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -1994,7 +1994,7 @@
                 style   = {error_style}
                 onClose = {=>@props.actions.setState(error:'')} />

-    render_not_collaborator_error: ->
+    render_access_error: ->
         public_view = @props.get_my_group(@props.project_id) == 'public'
         if public_view
             if @props.redux.getStore('account').is_logged_in()
@@ -2022,7 +2022,7 @@
             quotas = @props.get_total_project_quotas(@props.project_id)
             switch error
                 when '"not_public"'
-                    e = @render_not_collaborator_error()
+                    e = @render_access_error()
                 when 'no_dir'
                     e = <ErrorDisplay title="No such directory" error={"The path #{@props.current_path} does not exist."} />
                 when 'not_a_dir'
@@ -2163,5 +2163,5 @@
             {@render_paging_buttons(Math.ceil(listing.length / file_listing_page_size)) if listing?}
             {@render_file_listing(visible_listing, file_map, error, project_state, public_view)}
             {@render_paging_buttons(Math.ceil(listing.length / file_listing_page_size)) if listing?}
-            <div>{@render_not_collaborator_error() if public_view}</div>
+            {@render_access_error() if public_view}
         </div>


evaluate case formula

evaluate case formula

--- src/evaluator.coffee
+++ src/evaluator.coffee
@@ -152,6 +152,11 @@
   'isblank': (p) -> !p[0]? or p[0] is ''
   'nullvalue': (p) -> if p[0]? then p[0] else p[1]
   'blankvalue': (p) -> if p[0]? and p[0] isnt '' then p[0] else p[1]
+  'case': (p) ->
+    elseIndex = p.length - 1
+    for i in [1...elseIndex] when i % 2 is 1
+      return p[i+1] if p[i] is p[0]
+    return p[elseIndex]
 }

 evaluate = (formula, data) ->


Set user-agent from env

Set user-agent from env

--- server/lib/download.coffee
+++ server/lib/download.coffee
@@ -8,6 +8,10 @@
     fail: 0

 STATS_MULTIPLIER = 1 - 1.0/100
+
+isDev = !process.env.NODE_ENV || process.env.NODE_ENV == 'development'
+mode = if isDev then "dev" else "clone"
+USER_AGENT = process.env.USER_AGENT || "algoprog.ru [#{mode}]"

 addStats = (type) ->
     for t of statistics
@@ -24,7 +28,8 @@
     delay = 5
     maxAttempts = options?.maxAttempts || 1
     options.headers = options.headers || {}
-    options.headers["User-Agent"] = "algoprog.ru"
+    options.headers["User-Agent"] = USER_AGENT
+    console.log options
     for i in [1..maxAttempts]
         try
             page = await request({


Fix double Alarm::migrateDoctype on auto-merge.

--- server/models/alarm.coffee
+++ server/models/alarm.coffee
@@ -19,23 +19,6 @@

 require('cozy-ical').decorateAlarm Alarm

-# @TODO: migration script.
-# Add a doctype version somewhere ?
-Alarm::migrateDoctype = () ->
-    # Skip buggy or empty values.
-    if not @trigg
-        return @
-
-    # Check if it's already ISO8601
-    if (@trigg.charAt 10) is 'T'
-        return @
-
-    d = @trigg
-    # Check for a timezone
-    if "GMT" not in d
-        d = d + " GMT+0000"
-    @trigg = new Date(d).toISOString()
-    return @

 Alarm.all = (params, callback) ->
     Alarm.request "all", params, callback


animate connections from schools to recruits

--- source/javascripts/county-map.coffee
+++ source/javascripts/county-map.coffee
@@ -1,6 +1,4 @@
 render = (event, env) ->
-  colors = ['#a634f4', '#5adacc', '#bcf020', '#eeb016', '#ec180c']
-
   map = d3.select('#county-map').append('svg')
     .attr('width', env.width)
     .attr('height', env.height)
@@ -18,7 +16,7 @@
   # Returns a GeoJSON LineString object
   lineStringFromPlayerToSchool = (player, school) ->
     type: 'LineString'
-    coordinates: [[school.lat, school.lon], [player.lat, player.lon]]
+    coordinates: [[parseFloat(school.lat), parseFloat(school.lon)], [parseFloat(player.lat), parseFloat(player.lon)]]
     properties: player

   # Draw Great Arcs to recruit locations from the school.
@@ -41,9 +39,17 @@
       .data(recruitFeatures, (d) -> "#{d.properties.name}:#{d.properties.school}")

     connections.enter().append('path')
+      .attr('d', env.path)
+      .each(-> pathlen = @getTotalLength())
       .attr('class', (d) -> "connection stars#{d.properties.stars}")
-      .style('stroke', (d) -> colors[d.properties.stars - 1])
-      .attr('d', env.path)
+      .style('stroke', (d) -> env.colors[d.properties.stars - 1])
+      .attr('stroke-dasharray', -> len = @getTotalLength(); "#{len},#{len}")
+      .attr('stroke-dashoffset', -> @getTotalLength())
+    .transition()
+      .duration(100)
+      .delay((d, i) -> i / numRecruits * 200)
+      .attr('stroke-dashoffset', 0)
+

     connections.exit().remove()

@@ -54,16 +60,13 @@
       .attr('cx', (d) -> d.coordinates[0])
       .attr('cy', (d) -> d.coordinates[1])
       .attr('r', 0)
-      .style('fill', (d) -> colors[d.stars - 1])
+      .style('fill', '#fff')
       .attr('class', 'recruit')
     .transition()
-      .delay((d, i) -> i / numRecruits * 300)
-      .style('fill', '#fff')
-      .attr('r', 6)
-    .transition()
+      .delay((d, i) -> i / numRecruits * 200)
+      .duration(200)
+      .style('fill', (d) -> env.colors[d.stars - 1])
       .attr('r', 3)
-      .ease('bounce')
-      .style('fill', (d) -> colors[d.stars - 1])

     recruitNodes.exit().remove()


Fix selection bug

Fix selection bug

--- src/component.coffee
+++ src/component.coffee
@@ -350,10 +350,8 @@
     row = @getRowForEvent event
     return unless row
     content = @get('bodyContent') or []
-    oldSelection = @get 'selection'
-    if oldSelection
-      oldRow = content.findProperty('content', oldSelection)
-      oldRow.set 'isSelected', no
+    oldRow = content.findProperty('content', @get('selection'))
+    oldRow.set('isSelected', no) if oldRow
     row.set 'isSelected', yes
     @set 'selection', row.get('content')


- bit of cleanup - added "isFileProjectMainFile" method to determine , well, if a file is the main project file inside a folder

--- src/localStore.coffee
+++ src/localStore.coffee
@@ -10,7 +10,11 @@

 logger = require "usco-kernel/logger"
 logger.level = "debug"
-
+
+
+###*
+  * Class for storage on local hdd (won't work in browser)
+###
 class LocalStore extends StoreBase
   constructor:(options)->
     options = options or {}
@@ -124,7 +128,6 @@
       return true

     return promise
-

   ###*
   * delete the item at the given uri
@@ -183,9 +186,9 @@

     return fullPath

-
-  ###*
-  * checks if specified uri is the uri of a project: the folder needs to exist, and to contain a file with the same name as the folder + one of the "code extensions"
+  ###*
+  * checks if specified uri is the uri of a project: the folder needs to exist,
+  * and to contain a file with the same name as the folder + one of the "code extensions"
   * to qualitfy
   * @param {String} uri absolute uri of the path to check
   * @return {Object} "true" if given uri is a project, "false" otherwise
@@ -201,8 +204,24 @@
           if fs.existsSync( mainFile )
             return true
     return false
-
-
+
+  ###*
+  * checks if specified uri is the uri of a project's MAIN file
+  * @param {String} uri absolute uri of the path to check
+  * @return {Object} "true" if given uri is a project's main file, "false" otherwise
+  ###
+  isFileProjectMainFile:( uri )=>
+    if fs.existsSync( uri )
+      stats = fs.statSync( uri )
+      if not stats.isDirectory()
+        parentDir = path.dirname( uri )
+        baseName = path.basename( uri )
+
+        stats = fs.statSync( parentDir )
+        if parentDir is baseName and stats.isDirectory()
+          return true
+    return false
+
   #OLD #TODO: should project "class" instanciation take place here or else where ?????
   ###
   saveProject:( project, path )=>


OVirtConnection: fix API setup.

OVirtConnection: fix API setup.

--- src/OVirtConnection.coffee
+++ src/OVirtConnection.coffee
@@ -27,7 +27,7 @@
   # @property [OVirtApi] oVirt API root resource
   #
   get api: ->
-    @_api = new @_OVirtApi @ unless @_api
+    @_api = new @_OVirtApi $connection: @ unless @_api
     @_api

   #


Precence: bulk; prevent out-of-office autoresponders. Fix #178

--- lib/notifications.coffee
+++ lib/notifications.coffee
@@ -1,4 +1,9 @@
 @Notifications = new Mongo.Collection 'notifications'
+
+autoHeaders =
+  'Auto-Submitted': 'auto-generated'
+  Precedence: 'bulk'
+  #'X-Auto-Response-Suppress': 'OOF'

 if Meteor.isServer
   Notifications._ensureIndex [['to', 1], ['seen', 1], ['message', 1]]
@@ -468,6 +473,7 @@
       subject: '[Coauthor] ' + subject
       html: html
       text: text
+      headers: autoHeaders

   ## Reschedule any leftover notifications from last server run.
   Meteor.startup ->


Stub Indicator.hasData to make test valid

--- server/test/controllers/theme.coffee
+++ server/test/controllers/theme.coffee
@@ -81,6 +81,8 @@
   ).then(->
     Q.nsend(externalIndicator, 'save')
   ).then(->
+    hasDataStub = sinon.stub(Indicator::, 'hasData', -> Q.fcall(->true))
+
     stubReq = {}

     stubRes = {
@@ -98,8 +100,10 @@
           assert.strictEqual indicator._id.toString(), primaryIndicator.id,
             "Expected the returned indicator to be the primary indicator"

+          hasDataStub.restore()
           done()
         catch err
+          hasDataStub.restore()
           done(err)
     }


Use on() instead of live()

Use on() instead of live()

--- src/account/summary.coffee
+++ src/account/summary.coffee
@@ -135,19 +135,18 @@

 		# Upon the user expanding/collapsing a summary line,
 		# we will refresh/empty its inner plugins.
-		# FIXME: even though 'live' is deprecated in favour of 'on', using 'on'
-		# doesn't collapse.
-		$('.account_summary', @).live
+		$(@).on
 			expand: ->
 				$('.plugin', @).refresh()
 				# When expanding, the summary line itself should be refreshed,
 				# by having the inner plugin publish on a topic name that is
 				# determined by the h3 element id.
 				.publish "account.#{$('h3', @).prop 'id'}"
-				return false
+				return
 			collapse: ->
 				$('.plugin', @).empty()
-				return false
+				return
+			, '.account_summary'

 		# Upon the user logging in,
 		# we will refresh a summary line's inner plugin content if the summary line is not collapsed.


Try out icons in nav bar

Try out icons in nav bar

--- lib/settings-view.coffee
+++ lib/settings-view.coffee
@@ -41,10 +41,10 @@
     @openDotAtom.on 'click', ->
       atom.open(pathsToOpen: [atom.getConfigDirPath()])

-    @addPanel('General Settings', new GeneralPanel)
-    @addPanel('Keybindings', new KeybindingPanel)
-    @addPanel('Packages', new PackagesPanel(@packageManager))
-    @addPanel('Themes', new ThemePanel(@packageManager))
+    @addPanel('General Settings', 'settings', new GeneralPanel)
+    @addPanel('Keybindings', 'keyboard', new KeybindingPanel)
+    @addPanel('Packages', 'package', new PackagesPanel(@packageManager))
+    @addPanel('Themes', 'paintcan', new ThemePanel(@packageManager))

     packages = atom.packages.getLoadedPackages()
     # Include disabled packages so they can be re-enabled from the UI
@@ -77,8 +77,18 @@
     @panelMenu.append $$ ->
       @div class: 'panel-menu-separator'

-  addPanel: (name, panel) ->
-    panelItem = $$ -> @li name: name, => @a name
+  addPanel: (name, iconName, panel) ->
+    if arguments.length is 2
+      panel = iconName
+      iconName = null
+
+    panelItem = $$ ->
+      @li name: name, =>
+        if iconName
+          @a class: "icon icon-#{iconName}", name
+        else
+          @a name
+
     @panelMenu.append(panelItem)
     panel.hide()
     @panelsByName[name] = panel


the layout of the top in files is messed up due to the new upload button

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -1903,10 +1903,12 @@
                 create_file   = {@create_file}
                 create_folder = {@create_folder} />
             <Button
-                bsStyle={style}
-                onClick={@props.actions.toggle_upload}
-                active={@props.show_upload}
-            ><Icon name='upload' /> Upload</Button>
+                bsStyle = {style}
+                onClick = {@props.actions.toggle_upload}
+                active  = {@props.show_upload}
+                >
+                <Icon name='upload' /> Upload
+            </Button>
         </Col>

     render_activity: ->
@@ -2071,7 +2073,7 @@
                 <Col sm={if public_view then 6 else 3}>
                     <ProjectFilesPath current_path={@props.current_path} actions={@props.actions} />
                 </Col>
-                {<Col sm=4>
+                {<Col sm=3>
                     <div style={height:0}>  {#height 0 so takes up no vertical space}
                         <UsersViewing project_id={@props.project_id} />
                     </div>


Fixed error with undefined class

Fixed error with undefined class

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -1547,7 +1547,7 @@
         @props.actions.zip_files
             src  : @props.checked_files.toArray()
             dest : misc.path_to_file(@props.current_path, destination)
-            cb   : (err) ->
+            cb   : (err) =>
                     if err
                         @props.actions.set_activity(id:misc.uuid(), error: err)
                         return


file search -- support undefined properly

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -379,7 +379,7 @@
     # TODO: Make better help text
     render_help_alert : ->
         last_folder_index = @props.file_search?.lastIndexOf('/')
-        if @props.file_search?.indexOf('\\') != -1
+        if @props.file_search? and @props.file_search.indexOf('\\') != -1
             <Alert style={marginTop: '10px', fontWeight : 'bold'} bsStyle='danger'>
                 Warning: \ is an illegal character
             </Alert>


Add comment for package.json spec

Add comment for package.json spec

--- src/spec/package.json.spec.coffee
+++ src/spec/package.json.spec.coffee
@@ -1 +1,2 @@
+# used for JSON format validation only
 require '../package.json'


Support for 'neft:function' added to the XML parser

Now neft:function body is a XML CDATA and may contains xml reserved characters.

--- Element/element/parser.coffee
+++ Element/element/parser.coffee
@@ -97,6 +97,7 @@
 			recognizeSelfClosing: true
 			lowerCaseAttributeNames: false
 			lowerCaseTags: false
+			recognizeCDATA: true

 		parser.onattribname = do (_super = parser.onattribname) -> (name) ->
 			_super.call @, name
@@ -112,6 +113,10 @@
 				@_attribvalue = 'true'
 			_super.call @

+		# support neft:function
+		html = html.replace /<neft:function([^>]*)>/g, '$&<![CDATA['
+		html = html.replace /<\/neft:function>/gi, ']]>$&'
+
 		parser.write html
 		parser.end()


:new: Add hasLinter method

:new: Add hasLinter method

--- lib/linter-plus.coffee
+++ lib/linter-plus.coffee
@@ -50,6 +50,9 @@
   deleteLinter: (linter) ->
     @linters.delete(linter)

+  hasLinter: (linter) ->
+    @linters.has(linter)
+
   getLinters: ->
     @linters


Codo made to respect overloads not specifying method kind (fixes #144)

--- lib/meta/method.coffee
+++ lib/meta/method.coffee
@@ -25,7 +25,7 @@
         'static'

     options =
-      name: entry.signature.replace /^.([^\(]+)\(.+/, '$1'
+      name: entry.signature.replace /^[\#\.]?([^\(]+)\(.+/, '$1'
       kind: kind || ''
       parameters: Parameter.fromSignature(entry.signature).map (x) -> x.toString()
       documentation: entry.documentation


ResultView: Fix SVGTransform

ResultView: Fix SVGTransform

* Use `ParentNode.firstElementChild` instead of `ParentNode.firstChild`.

--- lib/result-view.coffee
+++ lib/result-view.coffee
@@ -210,10 +210,15 @@
 transformimeJupyter = require 'transformime-jupyter-transformers'

 SVGTransform = (mimetype, value, document) ->
-    el = document.createElement 'img'
-    el.setAttribute 'src',
-        "data:image/svg+xml;base64," + (new Buffer value).toString 'base64'
-    return el
+    container = document.createElement 'div'
+    container.innerHTML = value
+
+    svgElement = container.firstElementChild
+    unless svgElement?.tagName is 'svg'
+        throw new Error 'SVGTransform: Error: Failed to create an <svg> element'
+
+    return svgElement
+
 SVGTransform.mimetype = 'image/svg+xml'

 transformimeJupyter.consoleTextTransform.mimetype = [


Fix minor spelling mistake in spec

Fix minor spelling mistake in spec

--- spec/python-tools-spec.coffee
+++ spec/python-tools-spec.coffee
@@ -86,7 +86,7 @@
       [notification] = atom.notifications.getNotifications()
       expect(notification.type).toBe 'info'

-    it "informs the user with an error notifiation on invalid type", ->
+    it "informs the user with an error notification on invalid type", ->
       pythonTools.handleJediToolsResponse(
         type: "monkeys"
         definitions: [{


fix for travisCI test failure

fix for travisCI test failure

--- spec/src/debugger-spec.coffee
+++ spec/src/debugger-spec.coffee
@@ -21,6 +21,8 @@
       }

       atomStub =
+        project:
+          resolvePath: (file) -> '/path/to/' + file
         workspace:
           getActiveTextEditor: ->
             getPath: -> '/path/to/file.js'


jscocoa nsstrings aren't js strings =/

jscocoa nsstrings aren't js strings =/

--- src/osx.coffee
+++ src/osx.coffee
@@ -118,7 +118,7 @@
     if path?
       OSX.NSFileManager.defaultManager.changeCurrentDirectoryPath path
     else
-      OSX.NSFileManager.defaultManager.currentDirectoryPath
+      OSX.NSFileManager.defaultManager.currentDirectoryPath.toString()

   env: ->
     OSX.NSProcess.processInfo.environment


Fix logout uri

Fix logout uri

--- frontend/app/scripts/ServerAPI.coffee
+++ frontend/app/scripts/ServerAPI.coffee
@@ -35,7 +35,7 @@
     delete_auth: ->
       @ajax {
         type: "DELETE"
-        url: "/api/auth"
+        url: "/auth"
       }

     get_rooms: ->


Catch exceptions while running mapping.

Catch exceptions while running mapping.

--- src/pie.coffee
+++ src/pie.coffee
@@ -80,9 +80,10 @@
     unchangedFiles = _.without(files, changedFiles)

     run = (src, innerCb) ->
-      m.run src, processDest(m, src), (err) ->
-        return innerCb(err) if err
-        innerCb(null)
+      try
+        m.run(src, processDest(m, src), innerCb)
+      catch err
+        innerCb(err)

     if m.options.batch
       console.log "batching"


implementing IE8 script onload workaround

--- src/sdk.coffee
+++ src/sdk.coffee
@@ -273,7 +273,7 @@
       elem = document.createElement("script")
       elem.async = true
       elem.src = src
-      SC.Helper.attachEvent(elem, "load", callback)
+      SC.Helper.attachLoadEvent(elem, callback)
       document.body.appendChild(elem)
       elem

@@ -291,11 +291,13 @@
       options2.push(k + "=" + v) for own k, v of options
       window.open(url, "connectWithSoundCloud", options2.join(", "))

-    attachEvent: (element, eventName, func) ->
-      if element.attachEvent
-        element.attachEvent("on" + eventName, func)
-      else
-        element.addEventListener(eventName, func, false)
+    attachLoadEvent: (element, func) ->
+      if element.addEventListener
+        element.addEventListener("load", func, false)
+      else
+        element.onreadystatechange = ->
+          if this.readyState == "complete"
+            func()

     millisecondsToHMS: (ms) ->
       hms = {


Remove duplicate of main.coffee

Remove duplicate of main.coffee

--- lib/keybinding-resolver.coffee
+++ lib/keybinding-resolver.coffee
@@ -1,13 +0,0 @@
-KeyBindingResolverView = require './keybinding-resolver-view'
-
-module.exports =
-  keybindingResolverView: null
-
-  activate: (state) ->
-    @keybindingResolverView = new KeyBindingResolverView(state)
-
-  deactivate: ->
-    @keybindingResolverView.destroy()
-
-  serialize: ->
-    @keybindingResolverView.serialize()


Updated generated stitch info

Updated generated stitch info

--- lib/generator/index.coffee
+++ lib/generator/index.coffee
@@ -15,6 +15,7 @@
     "titanium-backbone": "git+ssh://git@github.com:trabian/titanium-backbone.git#master"
     "stitch-up": "git+ssh://git@github.com:trabian/stitch-up.git#master"
   stitch:
+    identifier: 'mobileRequire'
     output:
       app: "Resources/app-impl.js"
       vendor: "Resources/lib"
@@ -74,7 +75,21 @@
               writeFile 'package.json', (pd.json JSON.stringify buildPackage(options)), callback

             (callback) ->
-              mkdir 'src', callback
+              mkdir 'src', ->
+
+                async.parallel [
+
+                  (childCallback) ->
+                    mkdir 'src/app', ->
+                      writeFile 'src/app/index.coffee', '''
+                        module.exports =
+                          run: ->
+                            alert 'Hello World!'
+                      ''', childCallback
+
+                  (childCallback) -> mkdir 'src/properties', childCallback
+
+                ], callback

             (callback) ->
               mkdir 'tmp', ->
@@ -84,6 +99,7 @@
               mkdir 'Resources', ->
                 writeFile 'Resources/app.js', '''
                   Ti.include('app-impl.js');
+                  this.mobileRequire('index').run();
                 ''', callback

             (callback) ->
@@ -115,9 +131,8 @@
                 $ cd #{options.dir}
                 $ npm install

-              Now you're ready to edit the generated tiapp.xml and run a blank mobile app:
+              Now you're ready to run a blank mobile app:

-                $ vim tiapp.xml
                 $ cake t:iphone:run

             """


Fixed issue #15

Fixed issue #15

--- lib/terminal-panel-view.coffee
+++ lib/terminal-panel-view.coffee
@@ -167,8 +167,8 @@

   updateOnDidExitWorkspace: ->
     @hide()
-    @activeTerminalSubscrs.dispose()
-    @activeLanguageSubscrs.dispose()
+    @activeTerminalSubscrs?.dispose()
+    @activeLanguageSubscrs?.dispose()
     @activeTerminal = null
     @activeLanguage = null
     @fontSizeSelect.off()


Deprecation fix

Deprecation fix

--- lib/haskell-ghc-mod.coffee
+++ lib/haskell-ghc-mod.coffee
@@ -52,7 +52,7 @@
       @editorMap.set(editor,new EditorController(@process,editor))

   unregisterEdtiorCommands: ->
-    for editor in atom.workspace.getEditors()
+    for editor in atom.workspace.getTextEditors()
       @editorMap?.get(editor)?.desrtoy?()
     @subscriptions_editor?.dispose()


add multi mixin array notation api

add multi mixin array notation api

--- source/object.coffee
+++ source/object.coffee
@@ -126,7 +126,13 @@

   # Mixin properties and methods to the class prototype and merge
   # properties that are plain objects to support the mixin of configs etc.
-  @mixin: (mixin) ->
+  @mixin: (mixins) ->
+    if _.isArray(mixins)
+      @_addMixin(mixin) for mixin in mixins
+    else
+      @_addMixin(mixins)
+
+  @_addMixin: (mixin) ->

     # Create a clone so that we can remove properties without affecting the global mixin
     mixin = _.clone mixin


Load user features alongside id and email.

Fixes a bug where project features were not applied properly,
and instead fell back to the free-account defaults.

--- services/web/app/coffee/Features/Collaborators/CollaboratorsHandler.coffee
+++ services/web/app/coffee/Features/Collaborators/CollaboratorsHandler.coffee
@@ -30,7 +30,7 @@
 			return callback(error) if error?
 			return callback null, members.map (m) -> m.id

-	USER_PROJECTION: { _id: 1, email: 1 }
+	USER_PROJECTION: { _id: 1, email: 1, features: 1}
 	getMembersWithPrivilegeLevels: (project_id, callback = (error, members) ->) ->
 		CollaboratorsHandler.getMemberIdsWithPrivilegeLevels project_id, (error, members = []) ->
 			return callback(error) if error?


remove comment

remove comment

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


Simplify tests by reusing component loader

--- spec/Subgraph.coffee
+++ spec/Subgraph.coffee
@@ -371,7 +371,8 @@
     grInitials.addInitial 'initial-value', 'SplitIn', 'in'
     grInitials.addEdge 'SplitIn', 'out', 'SplitOut', 'in'

-    it 'should send defaults', (done) ->
+    cl = null
+    before (done) ->
       @timeout 6000
       cl = new noflo.ComponentLoader root
       cl.listComponents (err, components) ->
@@ -379,51 +380,42 @@
         cl.components.Split = createSplit
         cl.components.Defaults = grDefaults
         cl.components.Initials = grInitials
-        cl.load 'Defaults', (err, inst) ->
-          o = noflo.internalSocket.createSocket()
-          inst.outPorts.out.attach o
-          o.once 'data', (data) ->
-            chai.expect(data).to.equal 'default-value'
-            done()
-          inst.start (err) ->
-            return done err if err
+        done()
       return

+    it 'should send defaults', (done) ->
+      cl.load 'Defaults', (err, inst) ->
+        o = noflo.internalSocket.createSocket()
+        inst.outPorts.out.attach o
+        o.once 'data', (data) ->
+          chai.expect(data).to.equal 'default-value'
+          done()
+        inst.start (err) ->
+          return done err if err
+      return
+
     it 'should send initials', (done) ->
-      @timeout 6000
-      cl = new noflo.ComponentLoader root
-      cl.listComponents (err, components) ->
-        return done err if err
-        cl.components.Split = createSplit
-        cl.components.Defaults = grDefaults
-        cl.components.Initials = grInitials
-        cl.load 'Initials', (err, inst) ->
-          o = noflo.internalSocket.createSocket()
-          inst.outPorts.out.attach o
-          o.once 'data', (data) ->
-            chai.expect(data).to.equal 'initial-value'
-            done()
-          inst.start (err) ->
-            return done err if err
+      cl.load 'Initials', (err, inst) ->
+        o = noflo.internalSocket.createSocket()
+        inst.outPorts.out.attach o
+        o.once 'data', (data) ->
+          chai.expect(data).to.equal 'initial-value'
+          done()
+        inst.start (err) ->
+          return done err if err
       return

     it 'should not send defaults when an inport is attached externally', (done) ->
       @timeout 6000
-      cl = new noflo.ComponentLoader root
-      cl.listComponents (err, components) ->
-        return done err if err
-        cl.components.Split = createSplit
-        cl.components.Defaults = grDefaults
-        cl.components.Initials = grInitials
-        cl.load 'Defaults', (err, inst) ->
-          i = noflo.internalSocket.createSocket()
-          o = noflo.internalSocket.createSocket()
-          inst.inPorts.in.attach i
-          inst.outPorts.out.attach o
-          o.once 'data', (data) ->
-            chai.expect(data).to.equal 'Foo'
-            done()
-          inst.start (err) ->
-            return done err if err
-          i.send 'Foo'
+      cl.load 'Defaults', (err, inst) ->
+        i = noflo.internalSocket.createSocket()
+        o = noflo.internalSocket.createSocket()
+        inst.inPorts.in.attach i
+        inst.outPorts.out.attach o
+        o.once 'data', (data) ->
+          chai.expect(data).to.equal 'Foo'
+          done()
+        inst.start (err) ->
+          return done err if err
+        i.send 'Foo'
       return


Automatically load all .ass files from spec directory

--- spec/ass-spec.coffee
+++ spec/ass-spec.coffee
@@ -1,37 +1,36 @@
+_ = require 'lodash'
+ASS = require 'lib-ass'
+{Directory} = require 'atom'
+fs = require 'fs'
 path = require 'path'
-ASS = require 'lib-ass'
-fs = require 'fs'
-_ = require 'lodash'

-fixtures = [
-  "issues"
-  # BLOCKS
-  "blocks/hr"
-  "blocks/headings"
-  "blocks/fenced-code"
-  "blocks/link-references"
-  "blocks/quotes"
-  "blocks/lists"
-  # INLINES
-  "inlines/code-spans"
-  "inlines/escapes"
-  "inlines/entities"
-  "inlines/emphasis"
-  "inlines/links"
-  "inlines/images"
-  "inlines/autolinks"
-  "inlines/line-breaks"
-  "inlines/textual-content"
-  # FLAVORS
-  "flavors/github"
-  "flavors/criticmark"
-  "flavors/yaml-front-matter"
-]
+# NOTE
+# Manually specify {fixtures} if you only want to run specific tests.
+# A {fixture} is a relative path + filename (without extension).
+# fixtures = [
+#   "issues"
+# ]

-# Overwrite fixtures, cause this is what I'm working on...
-# fixtures = [
-#   "flavors/criticmark"
-# ]
+# Automatically generate the {fixtures} array from the file system.
+# Include all .ass files found inside /spec/fixtures, by their relative path
+# but excluding the file extension. This {fixture} is used to generate
+# identifiers for tasks.
+unless fixtures
+  getFixturesFrom = (directoryPath = '') ->
+    results = []
+    directory = new Directory(path.join(__dirname, './fixtures' + directoryPath))
+    entries = directory.getEntriesSync()
+    for entry in entries
+      if entry.isFile()
+        filename = entry.getBaseName()
+        if filename.substr(-4) is '.ass'
+          fixture = directoryPath + "/" + filename.substr(0, filename.length - 4)
+          results.push fixture
+      else
+        results = results.concat(getFixturesFrom(directoryPath + "/" + entry.getBaseName()))
+    return results
+
+  fixtures = getFixturesFrom()

 describe "Markdown grammar", ->
   grammar = null
@@ -93,13 +92,13 @@
             for line, a in tokens
               for token, b in line
                 expectation = test.tokens[i]
+                # NOTE
+                # A token.value without a length has been created, and is
+                # ignored. I believe this happens when an optional capture in
+                # the grammar is empty. As far as I can tell, these can be
+                # safely ignored, because you would omit these (unexpected)
+                # tokens when writing manual tests.
                 if tokens[a][b].value.length
                   expect(tokens[a][b]).toEqual value: expectation.value, scopes: expectation.scopes
-                # else
-                  # NOTE
-                  # A token.value without a length has been created, and is ignored. I believe this happens when an optional capture in the grammar is empty. As far as I can tell, these can be ignored, because you would omit these (unexpected) tokens when writing manual tests.
-                  # console.log "=== expectation[#{i}] for tokens[#{a}][#{b}] doesn't exist"
-                  # console.log "--- value:'#{tokens[a][b].value}'"
-                  # console.log "--- scopes:'#{tokens[a][b].scopes}'"
                 i++
             return


take to first step of related reading on refresh my memory

--- src/flux/task.coffee
+++ src/flux/task.coffee
@@ -81,14 +81,17 @@
     # Returns the reading and it's step index for a given task's ID
     getReadingForTaskId: (taskId, id) ->
       steps = getSteps(@_steps[taskId])
-      taskStepIndex = _.findIndex(steps, (step) -> step.id is id )
+      {related_content} = TaskStepStore.get(id)
+
+      # replace findIndex with findLastIndex if we should be going to the
+      # most recent step of a related reading
+      relatedStepIndex = _.findIndex steps, (step) ->
+        (step.type is 'reading') and (_.isEqual(step.chapter_section, _.first(related_content).chapter_section))
+
       # should never happen if the taskId was valid
-      throw new Error('BUG: Invalid taskId.  Unable to find index') if taskStepIndex is -1
-      # Find the reading that appears before the given task
-      for i in [taskStepIndex..0] by -1
-        if steps[i].type is 'reading'
-          return {reading: steps[i], index:i}
-      return {}
+      throw new Error('BUG: Invalid taskId.  Unable to find index') unless relatedStepIndex > -1
+      # Find the first step of the related reading that appears before the given task
+      {reading: steps[relatedStepIndex], index: relatedStepIndex}

     getDefaultStepIndex: (taskId) ->
       steps = getSteps(@_steps[taskId])


Added collision spec

Added collision spec

--- spec/gamespec.coffee
+++ spec/gamespec.coffee
@@ -37,4 +37,15 @@
                                              [new Position([Game.WIDTH - Game.VELOCITY*.1, Game.HEIGHT/2], Game.WEST, .1)],
                                              [new Position([Game.WIDTH/2, Game.HEIGHT - Game.VELOCITY*.1], Game.NORTH, .1)],
                                              [new Position([0 + Game.VELOCITY*.1, Game.HEIGHT/2], Game.EAST, .1)]])
+
+    it "notifies the listeners about a collision", ->
+      listener = jasmine.createSpyObj('listener', ['notify'])
+      SynchronizedTime.setTimeForTesting(0)
+      game.addListener(listener)
+      game.start()
+      game.player0.addToLine(new Position([1, 0], Game.EAST, 0))
+      game.player1.addToLine(new Position([3, 0], Game.WEST, 0))
+      SynchronizedTime.setTimeForTesting(1)
+      game.timer_tick()
+      expect(listener.notify).toHaveBeenCalledWith(game.player1, game.player0, new Point(2,0))
 )


Failing test.

Failing test.

--- spec/git-spec.coffee
+++ spec/git-spec.coffee
@@ -259,6 +259,37 @@
         expect(repo.isStatusModified(status)).toBe false
         expect(repo.isStatusNew(status)).toBe false

+    fit 'caches the proper statuses when multiple project are open', ->
+      otherWorkingDirectory = copyRepository()
+
+      atom.project.setPaths([workingDirectory, otherWorkingDirectory])
+
+      waitsForPromise ->
+        atom.workspace.open('b.txt')
+
+      statusHandler = null
+      runs ->
+        repo = atom.project.getRepositories()[0]
+        console.log(repo.getPath())
+
+        statusHandler = jasmine.createSpy('statusHandler')
+        repo.onDidChangeStatuses statusHandler
+        repo.refreshStatus()
+
+      waitsFor ->
+        statusHandler.callCount > 0
+
+      runs ->
+        subDir = path.join(workingDirectory, 'dir')
+        fs.mkdirSync(subDir)
+
+        filePath = path.join(subDir, 'b.txt')
+        fs.writeFileSync(filePath, '')
+
+        status = repo.getCachedPathStatus(filePath)
+        expect(repo.isStatusModified(status)).toBe true
+        expect(repo.isStatusNew(status)).toBe false
+
   describe "buffer events", ->
     [editor] = []


Put the drag handlers in the correct order. That was driving me crazy.

--- src/draggable.coffee
+++ src/draggable.coffee
@@ -118,12 +118,12 @@
       # Stop the mousedown event
       false

-    handleElementClick: (e) =>
-      # Clicks should be cancelled if the last mousedown/mouseup interaction resulted in a drag
-      if @shouldCancelClick
-        # Cancel the click
-        e.stopImmediatePropagation()
-        false
+    handleDocumentMouseMove: (e) =>
+      # Trigger the start event, once
+      @handleDragStart(e) unless @dragStarted
+
+      # Trigger the drag event
+      @handleDrag(e)

     handleDocumentMouseUp: (e) =>
       # Stop listening for mouse events on the document
@@ -151,12 +151,12 @@
       # Clean up
       @cleanUp()

-    handleDocumentMouseMove: (e) =>
-      # Trigger the start event, once
-      @handleDragStart(e) unless @dragStarted
-
-      # Trigger the drag event
-      @handleDrag(e)
+    handleElementClick: (e) =>
+      # Clicks should be cancelled if the last mousedown/mouseup interaction resulted in a drag
+      if @shouldCancelClick
+        # Cancel the click
+        e.stopImmediatePropagation()
+        false

     #
     # Draggable events
@@ -201,12 +201,6 @@
       # Mark the drag as having started
       @dragStarted = true

-    handleDragStop: (e) ->
-      @cancelAnyScheduledDrag()
-
-      # Call any user-supplied stop callback
-      @getConfig().stop?(e)
-
     handleDrag: (e) ->
       @scheduleDrag =>
         # Call any user-supplied drag callback
@@ -221,6 +215,12 @@
         @$helper.css
           left: parseInt(@helperStartPosition .left) + delta.x
           top: parseInt(@helperStartPosition .top) + delta.y
+
+    handleDragStop: (e) ->
+      @cancelAnyScheduledDrag()
+
+      # Call any user-supplied stop callback
+      @getConfig().stop?(e)

     #
     # Validators


draw-ellipse

draw-ellipse

--- src/draw_grid.coffee
+++ src/draw_grid.coffee
@@ -39,7 +39,7 @@
     ]
   draw_arc = (width, x_sign, y_sign) ->
     "a#{width},#{width} 0 0,1 #{x_sign*width},#{y_sign*width} "
-  draw_flipper = (width, x_sign, y_sign) ->
+  draw_ellipse = (width, x_sign, y_sign) ->
     draw_arc(width, x_sign, y_sign) + draw_arc(width, -1*x_sign, -1*y_sign)

   draw_turtle_path = (i, j) ->
@@ -48,10 +48,10 @@
     y = j*cell_size
     path = "M#{x},#{y} "
     for offset in [[1,1], [1,-1], [-1,1], [-1,-1]]
-      path += draw_flipper(width, offset[0], offset[1])
+      path += draw_ellipse(width, offset[0], offset[1])
     breadth = cell_size * 2 / 3
-    path += "m#{width},0 " + draw_arc(breadth, -3 / 2,0) + draw_arc(breadth, 3 / 2,0)
-    S.path {class: "path-turtle", d: path, stroke: "#00ff00", fill: "#88ff88"}
+    path += "m#{width},0 " + draw_ellipse(breadth, -3 / 2,0)
+    S.path {class: "path-turtle", d: path, fill: "#88ff88"}

   grid_path = ""


cleanup code

cleanup code

--- src/dt-stream.coffee
+++ src/dt-stream.coffee
@@ -25,12 +25,13 @@
             @release() if @children
             @isnext = yes
         # get the order position of this entry
-        @idx = @parent?._stream.children ? -1
+        idx = @parent?._stream.children ? -1
         # placeholder for close
         @parent?._stream.children++
         # when this entry is ready resume parent
-        el.ready =>
-            @parent?._stream.emit('close', order:@idx+1)
+        el.ready ->
+            @parent?._stream.emit('close scope', order:idx+1)
+        @parent?._stream.release()

     emit: ->
         @order.emit(arguments...)
@@ -41,7 +42,8 @@
         @emit 'entry', {job, order:(++@children)}

     release: () =>
-        @emit 'release', {order:0} unless @released
+        return if @released
+        @emit 'open scope', {order:0}
         @released = yes

@@ -59,14 +61,16 @@
         @builder._stream.release()
         do @listen
         # register ready handler
-        @template.register 'ready', (tag, next) ->
-            # when tag is already in the dom its fine,
-            #  else wait until it is inserted into dom
-            if tag._stream_ready is yes
+        @template.register('ready', @approve_ready)
+
+    approve_ready: (tag, next) ->
+        # when tag is already in the dom its fine,
+        #  else wait until it is inserted into dom
+        if tag._stream_ready is yes
+            next(tag)
+        else
+            tag._stream_ready = ->
                 next(tag)
-            else
-                tag._stream_ready = ->
-                    next(tag)

     listen: () ->
         EVENTS.forEach (event) =>
@@ -105,7 +109,6 @@
             el._stream_ready?()
             el._stream_ready = yes

-
     ondata: (el, data) ->
         el._stream.write =>
             @write data
@@ -120,14 +123,14 @@

     onattr: (el, key, value) ->
         return unless el.isempty
+        return unless el._stream_ready is yes
         console.warn "attributes of #{el.toString()} don't change anymore"

     onend: () ->
-        if @closed? and @opened_tags is 0
-            @close()
-        else
-            @builder.closed = 'pending'
-            @closed = @close
+        return @close() if @closed? or @opened_tags is 0
+        # delay until last tag gets closed and written out
+        @builder.closed = 'pending'
+        @closed = @close


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/expirable.coffee
+++ src/expirable.coffee
@@ -7,7 +7,7 @@

   self.bind "update", ->
     if I.fadeOut
-      I.alpha = 5
+      I.alpha = 5 starting

     if I.duration != -1 && I.age >= I.duration
       I.active = false


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/expirable.coffee
+++ src/expirable.coffee
@@ -3,7 +3,7 @@
     duration: -1
     alpha: 1

-  startingAlph
+  startingAlpha = I.alpha

   self.bind "update", ->
     if I.fadeOut


minor change

minor change

--- library/membrane/visual.coffee
+++ library/membrane/visual.coffee
@@ -166,10 +166,9 @@
     # context object, which is defered to be done on the client site.
     deployContext: (context, symbol) ->
         assert _.isObject(context), "malformed context"
-        excess = ["scripts", "sources", "sheets", "styles"]
-        excess.push "caching" if _.isObject context.caching
-        excess.push "changes" if _.isArray context.changes
-        excess.push "invokes" if _.isArray context.invokes
+        aexcess = ["scripts", "sources", "sheets", "styles"]
+        bexcess = ["caching", "changes", "invokes", "metatag"]
+        assert not _.isEmpty excess = aexcess.concat bexcess
         prepared = JSON.stringify _.omit(context, excess)
         installer = "#{symbol} = #{prepared}".toString()
         runtime = "(#{coffee}).apply(this)".toString()