Update browser integration tests to work with Jasmine2

--- test/browser.spec.coffee
+++ test/browser.spec.coffee
@@ -56,7 +56,7 @@
         done()

   describe 'XHR', ->
-    it 'does not emit http events if networking issue occurs', ->
+    it 'does not emit http events if networking issue occurs', (done) ->
       err = null
       httpHeaders = false; httpData = false; httpError = false; httpDone = false
       svc = new AWS.S3(accessKeyId: 'akid', secretAccessKey: 'secret', maxRetries: 0)
@@ -118,10 +118,10 @@
       body = 'body string'
       s3.putObject {Key: key, Body: body, ContentType: 'text/html'}, (err, data) ->
         noError(err)
-        s3.deleteObject(Key: key).send(done)
-      s3.putObject {Key: key, Body: body, ContentType: 'text/html; charset=utf-8'}, (err, data) ->
-        noError(err)
-        s3.deleteObject(Key: key).send(done)
+        s3.deleteObject(Key: key).send ->
+          s3.putObject {Key: key, Body: body, ContentType: 'text/html; charset=utf-8'}, (err, data) ->
+            noError(err)
+            s3.deleteObject(Key: key).send(done)

     describe 'progress events', ->
       integration 'emits http(Upload|Download)Progress events (no phantomjs)', (done) ->
@@ -145,8 +145,7 @@
             expect(progress.length > 1).to.equal(true)
             expect(progress[0].total).to.equal(body.size)
             expect(progress[0].loaded > 10).to.equal(true)
-            s3.deleteObject(Key: key).send()
-            done()
+            s3.deleteObject(Key: key).send(done)

   describe 'AWS.DynamoDB', ->
     integration 'writes and reads from a table', (done) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -33,6 +33,6 @@
 test "should move player back and forth when keys are changed", ->
   ;

-test "should move "
+test "should move paye"

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -7,6 +7,6 @@
     width: 10
     height: 20

-  player.
+  player.include(C)

 module()


Add TE suggestions as content to the OF note field.

--- add-note-count-to-focus-task.coffee
+++ add-note-count-to-focus-task.coffee
@@ -34,14 +34,14 @@

     inboxNotebook.notes.length

-  _countTextExpanderSuggestions: ->
+  _findTextExpanderSnippetsForGroup: (group) ->
     TextExpander = Application('TextExpander')

-    suggestedSnippets = @_findByName(
-      name: 'Suggested Snippets'
+    group = @_findByName(
+      name: group
       source: TextExpander.groups
       multipleResultsMessage: (names) ->
-        "Got more than one 'Suggested Snippets' TextExpander group!
+        "Got more than one '#{group}' TextExpander group!
          Matching group names were : [#{names}]."
     )

@@ -49,9 +49,22 @@
     # In playing around with the TextExpander group, the way I'm accessing snippets didn't
     # seem quite right. This does appear to give the correct number but it may be by
     # coincidence. Please improve if it is necessary and you can.
-    suggestedSnippets.snippets.length
+    group.snippets

-  _updateOmniFocusTask: ({taskBasename, count}) ->
+  _countTextExpanderSuggestions: ->
+    @_findTextExpanderSnippetsForGroup('Suggested Snippets').length
+
+  _contentForTextExpanderSuggestions: ->
+    snippets = [].slice.call(@_findTextExpanderSnippetsForGroup('Suggested Snippets'))
+
+    foo = snippets.map((snippet) ->
+      snippet.name()
+    ).map((name) ->
+      "* #{name}"
+    )
+    foo.join('\n')
+
+  _findOmniFocusTask: (taskBasename) ->
     OmniFocus = Application('OmniFocus')

     routines = @_findByName(
@@ -69,7 +82,7 @@
          Matching project names were : [#{names}]."
     )

-    task = @_findBy(
+    @_findBy(
       query:
         completed: false
         name:
@@ -80,12 +93,22 @@
          Matching task names were : [#{names}]."
     )

-    task.name = "#{taskBasename} (#{count})"
+  _updateOmniFocusTaskTitle: ({taskBasename, content}) ->
+    task = @_findOmniFocusTask(taskBasename)
+    task.name = "#{taskBasename} (#{content})"
+
+  _updateOmniFocusTaskNote: ({taskBasename, content}) ->
+    task = @_findOmniFocusTask(taskBasename)
+    task.note = content

   run: ->
-    @_updateOmniFocusTask(
+    @_updateOmniFocusTaskTitle(
       taskBasename: 'Process TextExpander suggestions'
-      count: @_countTextExpanderSuggestions()
+      content: @_countTextExpanderSuggestions()
+    )
+    @_updateOmniFocusTaskNote(
+      taskBasename: 'Process TextExpander suggestions'
+      content: @_contentForTextExpanderSuggestions()
     )
     # MRF 2016-03-31
     # In the process of migrating away from Evernote. Turning this off.


feat: add support for several languages in code blocks

--- grammars/asciidoc.cson
+++ grammars/asciidoc.cson
@@ -261,6 +261,740 @@
         ]
       }

+      # Matches Go AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,go]
+      #   ----
+      #   ...
+      #   ----
+      {
+        'begin': '^\\[source,\\s*go(lang)?\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc'
+        'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+            'end': '\\1'
+            'endCaptures':
+              '0': 'name': 'support.asciidoc'
+            'contentName': 'source.go'
+            'patterns': ['include': 'source.go']
+          }
+        ]
+      }
+
+      # Matches Java AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,java]
+      #   ----
+      #  ...
+      #   ----
+      {
+        'begin': '^\\[source,\\s*java\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc'
+        'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+            'end': '\\1'
+            'endCaptures':
+              '0': 'name': 'support.asciidoc'
+            'patterns': ['include': 'source.java']
+          }
+        ]
+      }
+
+      # Matches Mardown AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,md]
+      #   ----
+      #  ...
+      #   ----
+      {
+        'begin':'^\\[source,\\s*(markdown|mdown|md)\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc'
+        'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+            'end': '\\1'
+            'endCaptures':
+              '0': 'name': 'support.asciidoc'
+            'contentName': 'source.gfm'
+            'patterns': ['include': 'source.gfm']
+          }
+        ]
+      }
+
+      # Matches YAML AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,yml]
+      #   ----
+      #  ...
+      #   ----
+      {
+        'begin':'^\\[source,\\s*ya?ml\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc'
+        'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+            'end': '\\1'
+            'endCaptures':
+              '0': 'name': 'support.asciidoc'
+            'contentName': 'source.yaml'
+            'patterns': ['include': 'source.yaml']
+          }
+        ]
+      }
+
+      # Matches CoffeScript AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,cofeescript]
+      #   ----
+      #  ...
+      #   ----
+      {
+        'begin':'^\\[source,\\s*coffee-?(script)?\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc'
+        'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+            'end': '\\1'
+            'endCaptures':
+              '0': 'name': 'support.asciidoc'
+            'contentName': 'source.coffee'
+            'patterns': ['include': 'source.coffee']
+          }
+        ]
+      }
+
+      # Matches JSON AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,json]
+      #   ----
+      #  ...
+      #   ----
+      {
+        'begin':'^\\[source,\\s*json\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc'
+        'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+            'end': '\\1'
+            'endCaptures':
+              '0': 'name': 'support.asciidoc'
+            'contentName': 'source.json'
+            'patterns': ['include': 'source.json']
+          }
+        ]
+      }
+
+      # Matches CSS AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,css]
+      #   ----
+      #  ...
+      #   ----
+      {
+        'begin':'^\\[source,\\s*css\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc'
+        'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+              'end': '\\1'
+              'endCaptures':
+                '0': 'name': 'support.asciidoc'
+              'contentName': 'source.css'
+              'patterns': ['include': 'source.css']
+          }
+        ]
+      }
+
+      # Matches LESS AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,less]
+      #   ----
+      #  ...
+      #   ----
+      {
+        'begin':'^\\[source,\\s*less\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc' 'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+            'end': '\\1'
+            'endCaptures':
+              '0': 'name': 'support.asciidoc'
+            'contentName': 'source.css.less'
+            'patterns': ['include': 'source.css.less']
+          }
+        ]
+      }
+
+      # Matches XML AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,xml]
+      #   ----
+      #  ...
+      #   ----
+      {
+        'begin':'^\\[source,\\s*xml\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc' 'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+            'end': '\\1'
+            'endCaptures':
+              '0': 'name': 'support.asciidoc'
+            'contentName': 'text.xml'
+            'patterns': ['include': 'text.xml']
+          }
+        ]
+      }
+
+      # Matches Rust AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,rust]
+      #   ----
+      #  ...
+      #   ----
+      {
+        'begin':'^\\[source,\\s*(rust|rs)\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc' 'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+            'end': '\\1'
+            'endCaptures':
+              '0': 'name': 'support.asciidoc'
+            'contentName': 'source.rust'
+            'patterns': ['include': 'source.rust']
+          }
+        ]
+      }
+
+      # Matches Erlang AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,erlang]
+      #   ----
+      #  ...
+      #   ----
+      {
+        'begin':'^\\[source,\\s*erlang\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc' 'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+            'end': '\\1'
+            'endCaptures':
+              '0': 'name': 'support.asciidoc'
+            'contentName': 'source.erlang'
+            'patterns': ['include': 'source.erlang']
+          }
+        ]
+      }
+
+      # Matches C# AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,cs]
+      #   ----
+      #  ...
+      #   ----
+      {
+        'begin':'^\\[source,\\s*cs(harp)?\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc' 'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+            'end': '\\1'
+            'endCaptures':
+              '0': 'name': 'support.asciidoc'
+            'contentName': 'source.cs'
+            'patterns': ['include': 'source.cs']
+          }
+        ]
+      }
+
+      # Matches PHP AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,php]
+      #   ----
+      #  ...
+      #   ----
+      {
+        'begin':'^\\[source,\\s*php\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc' 'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+            'end': '\\1'
+            'endCaptures':
+              '0': 'name': 'support.asciidoc'
+            'contentName': 'source.php'
+            'patterns': ['include': 'source.php']
+          }
+        ]
+      }
+
+      # Matches Shell AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,shell]
+      #   ----
+      #  ...
+      #   ----
+      {
+        'begin':'^\\[source,\\s*(sh|bash|shell)\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc' 'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+            'end': '\\1'
+            'endCaptures':
+              '0': 'name': 'support.asciidoc'
+            'contentName': 'source.shell'
+            'patterns': ['include': 'source.shell']
+          }
+        ]
+      }
+
+      # Matches Python AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,python]
+      #   ----
+      #  ...
+      #   ----
+      {
+        'begin':'^\\[source,\\s*py(thon)?\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc' 'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+            'end': '\\1'
+            'endCaptures':
+              '0': 'name': 'support.asciidoc'
+            'contentName': 'source.python'
+            'patterns': ['include': 'source.python']
+          }
+        ]
+      }
+
+      # Matches C AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,c]
+      #   ----
+      #  ...
+      #   ----
+      {
+        'begin':'^\\[source,\\s*c\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc' 'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+            'end': '\\1'
+            'endCaptures':
+              '0': 'name': 'support.asciidoc'
+            'contentName': 'source.c'
+            'patterns': ['include': 'source.c']
+          }
+        ]
+      }
+
+      # Matches C++ AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,cpp]
+      #   ----
+      #  ...
+      #   ----
+      {
+        'begin':'^\\[source,\\s*c(pp|\\+\\+)\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc' 'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+            'end': '\\1'
+            'endCaptures':
+              '0': 'name': 'support.asciidoc'
+            'contentName': 'source.cpp'
+            'patterns': ['include': 'source.cpp']
+          }
+        ]
+      }
+
+      # Matches Objectif C AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,objc]
+      #   ----
+      #  ...
+      #   ----
+      {
+        'begin':'^\\[source,\\s*(objc|objective-c)\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc' 'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+            'end': '\\1'
+            'endCaptures':
+              '0': 'name': 'support.asciidoc'
+            'contentName': 'source.objc'
+            'patterns': ['include': 'source.objc']
+          }
+        ]
+      }
+
+      # Matches Swift AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,swift]
+      #   ----
+      #  ...
+      #   ----
+      {
+        'begin':'^\\[source,\\s*swift\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc' 'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+            'end': '\\1'
+            'endCaptures':
+              '0': 'name': 'support.asciidoc'
+            'contentName': 'source.swift'
+            'patterns': ['include': 'source.swift']
+          }
+        ]
+      }
+
+      # Matches HTML AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,html]
+      #   ----
+      #  ...
+      #   ----
+      {
+        'begin':'^\\[source,\\s*html\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc' 'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+            'end': '\\1'
+            'endCaptures':
+              '0': 'name': 'support.asciidoc'
+            'contentName': 'text.html.basic'
+            'patterns': ['include': 'text.html.basic']
+          }
+        ]
+      }
+
+      # Matches Elixir AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,elixir]
+      #   ----
+      #  ...
+      #   ----
+      {
+        'begin':'^\\[source,\\s*elixir\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc' 'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+            'end': '\\1'
+            'endCaptures':
+              '0':'name': 'support.asciidoc'
+            'contentName': 'source.elixir'
+            'patterns': ['include': 'source.elixir']
+          }
+        ]
+      }
+
+      # Matches Diff/Patch AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,diff]
+      #   ----
+      #  ...
+      #   ----
+      {
+        'begin':'^\\[source,\\s*(diff|patch|rej)\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc' 'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+            'end': '\\1'
+            'endCaptures':
+              '0': 'name': 'support.asciidoc'
+            'contentName': 'source.diff'
+            'patterns': ['include': 'source.diff']
+          }
+        ]
+      }
+
+      # Matches Julia AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,julia]
+      #   ----
+      #  ...
+      #   ----
+      {
+        'begin':'^\\[source,\\s*julia\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc' 'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+            'end': '\\1'
+            'endCaptures':
+              '0': 'name': 'support.asciidoc'
+            'contentName': 'source.julia'
+            'patterns': ['include': 'source.julia']
+          }
+        ]
+      }
+
+      # Matches R AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,r]
+      #   ----
+      #  ...
+      #   ----
+      {
+        'begin':'^\\[source,\\s*r\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc' 'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+            'end': '\\1'
+            'endCaptures':
+              '0': 'name': 'support.asciidoc'
+            'contentName': 'source.r'
+            'patterns': ['include': 'source.r']
+          }
+        ]
+      }
+
+      # Matches Haskell AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,haskell]
+      #   ----
+      #  ...
+      #   ----
+      {
+        'begin':'^\\[source,\\s*haskell\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc' 'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+            'end': '\\1'
+            'endCaptures':
+              '0': 'name': 'support.asciidoc'
+            'contentName': 'source.haskell'
+            'patterns': ['include': 'source.haskell']
+          }
+        ]
+      }
+
+      # Matches ELM AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,elm]
+      #   ----
+      #  ...
+      #   ----
+      {
+        'begin':'^\\[source,\\s*elm\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc' 'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+            'end': '\\1'
+            'endCaptures':
+              '0': 'name': 'support.asciidoc'
+            'contentName': 'source.elm'
+            'patterns': ['include': 'source.elm']
+          }
+        ]
+      }
+
+      # Matches SQL AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,sql]
+      #   ----
+      #  ...
+      #   ----
+      {
+        'begin':'^\\[source,\\s*sql\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc' 'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+            'end': '\\1'
+            'endCaptures':
+              '0': 'name': 'support.asciidoc'
+            'contentName': 'source.sql'
+            'patterns': ['include': 'source.sql']
+          }
+        ]
+      }
+
+      # Matches Clojure AsciiDoc code blocks
+      #
+      # Examples
+      #
+      #   [source,clojure]
+      #   ----
+      #  ...
+      #   ----
+      {
+        'begin':'^\\[source,\\s*clojure\\]$'
+        'beginCaptures':
+          '0': 'name': 'support.asciidoc' 'end': '^\\s*$'
+        'patterns': [
+          {
+            'begin': '(^\\s*-{4,}\\s*$)'
+            'beginCaptures':
+              '0': 'name': 'support.asciidoc'
+            'end': '\\1'
+            'endCaptures':
+              '0': 'name': 'support.asciidoc'
+            'contentName': 'source.clojure'
+            'patterns': ['include': 'source.clojure']
+          }
+        ]
+      }
+
       # Matches language-agnostic AsciiDoc code blocks
       #
       # Examples


Changed: Atom: Move Nuclide toolbar to bottom

--- dotfiles/.atom/config.cson
+++ dotfiles/.atom/config.cson
@@ -80,6 +80,8 @@
       "nuclide-side-bar": false
   tabs:
     showIcons: false
+  "tool-bar":
+    position: "Bottom"
   "tree-view":
     hideIgnoredNames: true
     squashDirectoryNames: true


Fix indent

Fix indent

--- app/assets/javascripts/events.js.coffee
+++ app/assets/javascripts/events.js.coffee
@@ -8,11 +8,11 @@
   $('.jobs_controller.show_action').each ->
     event = new Altria.ServerEvent()
     event.on 'build.finished', (attributes) ->
-        $.ajax "/builds/#{attributes.id}", data: { type: 'list' }, success: (data) ->
-          $("#build#{attributes.id}").replaceWith(data)
+      $.ajax "/builds/#{attributes.id}", data: { type: 'list' }, success: (data) ->
+        $("#build#{attributes.id}").replaceWith(data)
     event.on 'build.started', (attributes) ->
-        $.ajax "/builds/#{attributes.id}", data: { type: 'list' }, success: (data) ->
-          $('.builds ul').prepend(data)
+      $.ajax "/builds/#{attributes.id}", data: { type: 'list' }, success: (data) ->
+        $('.builds ul').prepend(data)

   $('.builds_controller.show_action').each ->
     new Altria.ServerEvent()


Fix dropzone which throwed error if had no dropzone

--- app/assets/javascripts/images.js.coffee
+++ app/assets/javascripts/images.js.coffee
@@ -10,27 +10,28 @@
 # You can use CoffeeScript in this file: http://coffeescript.org/

 preload_images = ->
-  image_drop = Dropzone.forElement("#new_image")
-  imageable_type = $(image_drop.element).data("imageable-type")
-  imageable_id = $(image_drop.element).data("imageable-id")
+  if $("#new_image").length > 0
+    image_drop = Dropzone.forElement("#new_image")
+    imageable_type = $(image_drop.element).data("imageable-type")
+    imageable_id = $(image_drop.element).data("imageable-id")

-  $.get "/images.json",
-  image:
-    imageable_type: imageable_type
-    imageable_id: imageable_id
-  , (data) ->
-    $(data).each ->
-      # Create the mock file:
-      mockFile =
-        name: this.image_file_name
-        size: this.size
-        id: this.id
+    $.get "/images.json",
+    image:
+      imageable_type: imageable_type
+      imageable_id: imageable_id
+    , (data) ->
+      $(data).each ->
+        # Create the mock file:
+        mockFile =
+          name: this.image_file_name
+          size: this.size
+          id: this.id

-      # Call the default addedfile event handler
-      image_drop.emit "addedfile", mockFile
+        # Call the default addedfile event handler
+        image_drop.emit "addedfile", mockFile

-      # And optionally show the thumbnail of the file:
-      image_drop.emit "thumbnail", mockFile, this.url
+        # And optionally show the thumbnail of the file:
+        image_drop.emit "thumbnail", mockFile, this.url

 ready = ->
   Dropzone.autoDiscover = false


attempt to fill columns from top to bottom in client

--- app/assets/javascripts/issues.js.coffee
+++ app/assets/javascripts/issues.js.coffee
@@ -1,6 +1,17 @@
 $(document).on 'page:load ready page:fetch', ->
   $('#milestone-selector').change ->
     window.location = "?milestone=#{encodeURI($(@).val())}"
+
+  nColumns = $(".column-heading").length
+
+  # attempt to fill the rows from the top-->bottom
+  for i in [0..nColumns]
+    column = $(".portlet[data-col='#{i}']")
+
+    j = 1
+    for row in column
+      row.dataset.row = j
+      j++

   $('.gridster ul').gridster
     widget_selector: '.portlet'


Map show marker on current position.

Map show marker on current position.

--- app/assets/javascripts/places.js.coffee
+++ app/assets/javascripts/places.js.coffee
@@ -2,6 +2,12 @@
 # All this logic will automatically be available in application.js.
 # You can use CoffeeScript in this file: http://coffeescript.org/
 $(() ->
-  map = new qq.maps.Map document.getElementById('map-container')
-  map.panTo new qq.maps.LatLng 40, 120
+  map = new qq.maps.Map document.getElementById 'map-container'
+  navigator.geolocation.getCurrentPosition (position) ->
+    currentPosition = new qq.maps.LatLng position.coords.latitude, position.coords.longitude
+    map.panTo currentPosition
+    map.setZoom 15
+    new qq.maps.Marker
+      position: currentPosition
+      map: map
 )


supporting /ventas/:venta_id (not done yet)

--- app/assets/javascripts/router.js.coffee
+++ app/assets/javascripts/router.js.coffee
@@ -12,6 +12,7 @@
   @resource "ventas", ->
     @route "index", path: "/"
     @route "new"
+    @route "show", path: "/:venta_id"
   @resource "clientes", ->
     @route "index", path: "/"
     @route "new"


fix(config): Report JSON parsing errors in a dialog #2102

--- src/config.coffee
+++ src/config.coffee
@@ -11,8 +11,10 @@

 if global.application
   app = global.application
+  dialog = require('electron').dialog
 else
   app = remote.getGlobal('application')
+  dialog = remote.dialog

 # Essential: Used to access all of N1's configuration details.
 #
@@ -561,12 +563,13 @@
       @configFileHasErrors = true
       message = "Failed to load `#{path.basename(@configFilePath)}`"

-      detail = if error.location?
+      if error.location?
         # stack is the output from JSON in this case
-        error.stack
+        detail = error.stack
       else
         # message will be EACCES permission denied, et al
-        error.message
+        detail = error.message
+      detail += "\n\nMake sure the file (#{@configFilePath}) contains valid JSON, or delete it to reset N1."

       @notifyFailure(message, detail)

@@ -575,7 +578,7 @@
       @watchSubscription ?= pathWatcher.watch @configFilePath, (eventType) =>
         @requestLoad() if eventType is 'change' and @watchSubscription?
     catch error
-      @notifyFailure """
+      @notifyFailure "Configuration Error", """
         Unable to watch path: `#{path.basename(@configFilePath)}`. Make sure you have permissions to
         `#{@configFilePath}`. On linux there are currently problems with watch
         sizes.
@@ -586,7 +589,7 @@
     @watchSubscription = null

   notifyFailure: (errorMessage, detail) ->
-    console.log(errorMessage, detail)
+    dialog.showErrorBox(errorMessage, detail)

   save: ->
     manager = app.sharedFileManager


Load game on setup

Load game on setup

--- src/config.coffee
+++ src/config.coffee
@@ -34,11 +34,11 @@
     layout
     turtles
     sprites
-    game
   ]
 }

 setup = ->
+  baseline._CHILDREN.push game
   baseline

 exports.config = setup()


Handle errors on /try, rather than appearing to 'Load' forever.

--- app/assets/javascripts/tryEvalGD.coffee
+++ app/assets/javascripts/tryEvalGD.coffee
@@ -13,4 +13,6 @@
         "contentType": "application/json; charset=utf-8",
         "success": (data) =>
           $('#result').text(JSON.stringify(data, null, 4))
+        "error": (jqXHR, textStatus, errorThrown) =>
+          $('#result').text("[" + textStatus + "] " + errorThrown)
       }


Removed unused collection

Removed unused collection

--- core/app/backbone/controllers/profile_controller.coffee
+++ core/app/backbone/controllers/profile_controller.coffee
@@ -15,11 +15,8 @@
       if user.get('deleted')
         main_region.show new TextView text: 'This profile has been deleted.'
       else
-        channels = new ChannelList([], username: user.get('username'))
-        channels.fetch()
         main_region.show new ProfileView
           model: user
-          collection: channels
           created_facts_view: @getFactsView user

   notification_options: (username)->


Changed about 'username' to Profile in channel header so it's more consistent with other parts of the application.

--- core/app/backbone/controllers/profile_controller.coffee
+++ core/app/backbone/controllers/profile_controller.coffee
@@ -34,7 +34,7 @@

   # HELPERS
   profile_options: (username) ->
-    title: 'About ' + username
+    title: 'Profile'
     active_tab: 'show'
     render: (main_region, user) =>
       @makePermalinkEvent()


Forgot to remove the import in packs

Forgot to remove the import in packs

--- app/javascript/packs/application.coffee
+++ app/javascript/packs/application.coffee
@@ -4,7 +4,6 @@
 import 'src/cookie'
 import 'src/compat.jquery'
 import 'src/dmail'
-import 'src/favorite'
 import 'src/forum'
 import 'src/notice'


fixed the signature so it actually signs what we give it.

--- app/models/forgot-password-model.coffee
+++ app/models/forgot-password-model.coffee
@@ -15,10 +15,11 @@
       return callback new Error('Device not found for email address') if error? or !device?

       resetToken = @uuidGenerator.v4()
-      device[@uuid] = @sign device[@uuid]

       @bcrypt.hash resetToken + device.uuid, 10, (hash)=>
         device[@uuid].reset = hash
+        device[@uuid] = @sign device[@uuid]
+
         @db.update(
           {uuid : device.uuid}
           _.pick(device, @uuid)
@@ -32,10 +33,11 @@
           callback
         )

-  reset : (uuid, token, password, callback=->) =>
+  reset : (uuid, token, password, callback=->) =>
     @findSigned uuid: uuid, (error, device) =>
       return callback new Error('Device not found') if error? or !device?
       return callback new Error('Invalid Token') unless @bcrypt.compareSync(token + uuid, device[@uuid].reset)
+
       @bcrypt.hash password + uuid, 10, (hash) =>
         delete device[@uuid].reset
         device[@uuid].secret = hash
@@ -53,7 +55,7 @@
           @meshblu.verify(_.omit( device[@uuid], 'signature' ), device[@uuid]?.signature)

       callback null, device
-
+

   sign : (data) =>
     data = _.cloneDeep data


Fix broken ss when switching between views

When the two views have the same number of rows but some have been
deleted.

--- web/volume/spreadsheet.coffee
+++ web/volume/spreadsheet.coffee
@@ -1324,6 +1324,7 @@
         # Call all populate functions
         setKey = (key) ->
           unselect()
+          oldKey = Key
           Key = $scope.key = key? && getCategory(key) || pseudoCategory.slot
           foot = Rows[-1]
           Rows = []
@@ -1332,7 +1333,7 @@
             populateSlots()
           else
             populateRecords()
-          if Order.length != Rows.length
+          if Key != oldKey || Order.length != Rows.length
             Order = if Rows.length then [0..Rows.length-1] else []
           $scope.count = Rows.length
           $(TBody).empty()


Fix string == int comparison for ss record use check

Fixes #3900

--- web/volume/spreadsheet.coffee
+++ web/volume/spreadsheet.coffee
@@ -980,7 +980,7 @@
                   rs = []
                   mf = (r) -> (m) -> r.measures[m]
                   for ri, r of volume.records
-                    if r.category == info.c && !info.row.list(info.c).some((d) -> d.id == ri)
+                    if r.category == info.c && !info.row.list(info.c).some((d) -> `d.id == ri`)
                       rs.push
                         r:r
                         v:(r.measures[mi] ? '').toLowerCase()


Allow extra space after remember regex

Allow extra space after remember regex

--- scripts/nostalgia.coffee
+++ scripts/nostalgia.coffee
@@ -258,7 +258,7 @@
     res.send 'You rang?'

 module.exports = (robot) ->
-    robot.respond /Remember (?:that )?(.+) said "([^"]+)"$/i, rememberPerson
+    robot.respond /Remember (?:that )?(.+) said "([^"]+)"/i, rememberPerson

     robot.respond /Remind me of (.*)/i, msgRespond
     robot.respond /Quote (.*)/i, msgRespond


Disambiguate old vs. new Course screenshot image path format

Otherwise the course editor doesn't know which of two schemas to
validate against and throws an error.

--- app/schemas/models/course.schema.coffee
+++ app/schemas/models/course.schema.coffee
@@ -22,7 +22,7 @@
   pricePerSeat: {type: 'number', description: 'Price per seat in USD cents.'} # deprecated
   free: { type: 'boolean' }
   screenshot: oneOf: [
-    { type: 'string', format: 'image-file', title: 'Thumbnail image', description: 'Relevant for teacher dashboard' }
+    { type: 'string', format: 'image-file', title: 'Thumbnail image', description: 'Relevant for teacher dashboard', pattern: /^db.*/ }
     c.path { title: 'URL', description: 'Link to course screenshot.'} # deprecated
   ]
   adminOnly: { type: 'boolean', description: 'Deprecated in favor of releasePhase.' }


Disable Unlike-Like title change until Chris fixes the backend.

--- client/landing/app/Applications/Activity.kdapplication/views/activityactions.coffee
+++ client/landing/app/Applications/Activity.kdapplication/views/activityactions.coffee
@@ -85,8 +85,8 @@

           @likeCount.updateTooltip {title: tooltip }

-          if checkIfILiked
-            @likeLink.updatePartial if likedBefore then "Unlike" else "Like"
+          # if checkIfILiked
+          #   @likeLink.updatePartial if likedBefore then "Unlike" else "Like"

   viewAppended:->


Fixes #21 : removed pseudo-caching of representatives to prevent duplication errors.

Conflicts:
	app/scripts/controllers/district.coffee

--- app/scripts/controllers/district.coffee
+++ app/scripts/controllers/district.coffee
@@ -174,7 +174,7 @@
         console.log $routeParams.bioguide_id
         ApiGet.congress "legislators?bioguide_id=#{$routeParams.bioguide_id}", (error, data) ->
           if not error
-            if not data[0].district
+            if not data[0].district
               for state in ["AK", "DE", "MT", "ND", "SD", "VT", "WY"]
                 if data[0].state is state
                   data[0].district = "0"


Fix tests

Fix tests

--- test/type_checker.coffee
+++ test/type_checker.coffee
@@ -1,3 +1,8 @@
+reporter = require '../lib/reporter'
+parse = (coffee) ->
+  reporter.errors = []
+  parse coffee
+
 suite 'TypeChecker', ->
   suite 'Assignment', ->
     test 'basic assign', ->
@@ -15,7 +20,7 @@

     test 'throw type mismatch', ->
       throws ->
-        CoffeeScript.parse """
+        parse """
           x :: Number = "3"
         """

@@ -24,19 +29,19 @@

     test 'throw object literal mitmatching', ->
       throws ->
-        CoffeeScript.parse """
+        parse """
           obj :: { x :: Number } = { x : '' }
         """

     test 'throw at lacking of object member', ->
       throws ->
-        CoffeeScript.parse """
+        parse """
           obj :: { x :: Number, y :: Number } = { x : 3 }
         """

     test 'throw member access error', ->
       throws ->
-        CoffeeScript.parse """
+        parse """
           obj :: { x :: Number } = { x : 3 }
           obj.x = ""
         """
@@ -73,7 +78,7 @@

     test 'typed function mismatching application', ->
       throws ->
-        CoffeeScript.parse """
+        parse """
           f :: Number -> Number = 3
         """

@@ -123,7 +128,7 @@

     test 'throw struct member access with mismatch type', ->
       throws ->
-        CoffeeScript.parse """
+        parse """
           struct Point {
             x :: Number
             y :: Number
@@ -142,7 +147,7 @@

     test 'throw function arguments mismatch', ->
       throws ->
-        CoffeeScript.parse """
+        parse """
           f :: Number -> Number = (n :: Number) :: Number ->  n * n
           (f "hello")
         """
@@ -153,7 +158,7 @@

     test 'throw function arguments mismatch', ->
       throws ->
-        CoffeeScript.parse """
+        parse """
           f :: Number -> Number = (n :: Number) :: Number ->  n * n
           y :: String = (f 3)
         """
@@ -184,7 +189,7 @@

     test 'throw if return type mismatch', ->
       throws ->
-        CoffeeScript.parse """
+        parse """
           a :: Number = if true then 3 else ""
         """

@@ -204,13 +209,13 @@

     test 'throw return type mismatch', ->
       throws ->
-        CoffeeScript.parse """
+        parse """
           arr :: Number[] = ("" for i in [1,2,3])
         """

     test 'throw target mismatch', ->
       throws ->
-        CoffeeScript.parse """
+        parse """
           arr :: Number[] = (i for i :: Number in [1,2,""])
         """

@@ -220,7 +225,7 @@
           val

     test 'throw target mismatch', ->
-      throws -> CoffeeScript.parse """
+      throws -> parse """
       list :: Number[] =
         for key :: String, val :: Number of {x: "hoge", y: 6}
           val
@@ -235,12 +240,12 @@
         3

     test 'throw function return type mismatch', ->
-      throws -> CoffeeScript.parse """
+      throws -> parse """
       f0 :: () -> Number = () :: Number -> ''
       """

     test 'throw function return type mismatch', ->
-      throws -> CoffeeScript.parse """
+      throws -> parse """
       f2 :: () -> Number = ->
         return ""
       """
@@ -249,7 +254,7 @@
       list :: Number[] = [1..10]

     test 'throw function return type mismatch', ->
-      throws -> CoffeeScript.parse """
+      throws -> parse """
       list :: String[] = [1..10]
       """

@@ -260,7 +265,7 @@
       a :: String = "hello" + "world"

     test 'miscast BinOp', ->
-      throws -> CoffeeScript.parse """
+      throws -> parse """
       c :: Number = "" + 3
       """

@@ -274,7 +279,7 @@
           else
             'fuga'
     test 'miscast Switch', ->
-      throws -> CoffeeScript.parse """
+      throws -> parse """
       x :: Number =
         switch true
           when 0
@@ -284,7 +289,7 @@
       """

     test 'cant catch undefined', ->
-      throws -> CoffeeScript.parse """
+      throws -> parse """
       x :: Number = global?.require
       """

@@ -300,7 +305,7 @@
       n :: Number = x.f 3

     test 'new', ->
-      throws -> CoffeeScript.parse """
+      throws -> parse """
       class X
         f: (n :: Number) :: Number ->
           n * n
@@ -318,7 +323,7 @@
       s :: Numbertgra = origin.getInstance()

     test 'throw generics', ->
-      throws -> CoffeeScript.parse """
+      throws -> parse """
         struct Singleton<T> {
           getInstance :: () -> T
         }
@@ -340,7 +345,7 @@
       num :: Number = hash.get "a"

     test 'throw generics hash', ->
-      throws -> CoffeeScript.parse """
+      throws -> parse """
       struct Hash<K, V> {
         get :: K -> V
         set :: K * V -> ()
@@ -354,7 +359,7 @@
       """

     test 'throw generics hash', ->
-      throws -> CoffeeScript.parse """
+      throws -> parse """
       struct Hash<K, V> {
         get :: K -> V
         set :: K * V -> ()
@@ -367,7 +372,7 @@
       """

     test 'throw when function args mismatch', ->
-      throws -> CoffeeScript.parse """
+      throws -> parse """
       f :: Number * String -> String = (n, s) ->
         a :: Number = s
       """
@@ -377,7 +382,7 @@
       a = 3

     test 'throw pre-defined', ->
-      throws -> CoffeeScript.parse """
+      throws -> parse """
       a :: Number
       a = "str"
       """
@@ -387,7 +392,7 @@
       a = (n) -> n

     test 'throw pre-defined args mismatch', ->
-      throws -> CoffeeScript.parse """
+      throws -> parse """
       a :: Number -> Number
       a = (n) ->
         n2 :: String = n
@@ -401,7 +406,7 @@
           @text = n.toString()

     test 'throw pre-defined args mismatch in class', ->
-      throws -> CoffeeScript.parse """
+      throws -> parse """
       class X
         text :: String
         f :: Number -> Number
@@ -410,7 +415,7 @@
       """

     test 'throw generics object', ->
-      throws -> CoffeeScript.parse """
+      throws -> parse """
       struct Hash<K, V> {
         get :: K -> V
         set :: K * V -> ()
@@ -438,7 +443,7 @@
           @foo = 3

     test 'throw access this in class', ->
-      throws -> CoffeeScript.parse """
+      throws -> parse """
       class Z
         foo :: Number
         constructor: ->
@@ -446,9 +451,9 @@
       """

     test 'throw access proto this in class', ->
-      throws -> CoffeeScript.parse """
+      throws -> parse """
       class K
         bar :: String
         f : (n) ->
-          @bar = ""
-      """
+          @bar = 2
+      """


added a quick check to return an error back to the user if the yaml file is invalid

--- src/deploy.coffee
+++ src/deploy.coffee
@@ -57,6 +57,7 @@
   github.repos.getContent user: options.org, repo: options.repo, path: "#{options.yaml}.yml", ref: options.branch, (err, res) ->
     return cb(null) if err
     config = yaml.safeLoad(new Buffer(res.content, 'base64').toString())
+    return msg.reply "There is an error with your #{options.yaml}.yml file" if !config.deploy or !config.deploy[options.serverice]
     return cb(config.deploy[options.service])


direct: Only set up broker on connect()

direct: Only set up broker on connect()

Also add debug logging

--- src/direct.coffee
+++ src/direct.coffee
@@ -1,4 +1,5 @@

+debug = require('debug')('msgflo:direct')
 interfaces = require './interfaces'
 EventEmitter = require('events').EventEmitter

@@ -12,9 +13,11 @@

   ## Broker connection management
   connect: (callback) ->
+    debug 'client connect'
     @broker = brokers[@address]
     return callback null
   disconnect: (callback) ->
+    debug 'client disconnect'
     @broker = null
     return callback null

@@ -53,12 +56,15 @@
   constructor: (@address) ->
     @queues = {}
 #    console.log 'broker', @address
-    brokers[@address] = this

   ## Broker connection management
   connect: (callback) ->
+    debug 'broker connect'
+    brokers[@address] = this
     return callback null
   disconnect: (callback) ->
+    debug 'broker disconnect'
+    delete brokers[@address]
     return callback null

   ## Manipulating queues


Fixed Exeption error in chatpanel

Fixed Exeption error in chatpanel

--- lib/GUI/atox-chatpanel.coffee
+++ lib/GUI/atox-chatpanel.coffee
@@ -118,8 +118,7 @@
     else if atom.config.get('aTox.userAvatar') != 'none'
       # TODO add placeholder avatar
       @coverview.find("[cID='#{cID}']").css({'background-image': "url(#{atom.config.get 'aTox.userAvatar'})"})
-    for peer in params.peerlist
-      @addUser(peer.content)
+    @addUser peer.content for peer in params.peerlist if params.peerlist?

   selectChat: (cID) ->
     cID = "#{cID}"


popup: add listener system to avoid to use CSS global sibling selector

--- app/assets/javascripts/popup.coffee
+++ app/assets/javascripts/popup.coffee
@@ -7,9 +7,19 @@
   popupId = event.data("popup-id")
   allEvents = $(".popup-event[data-popup-id=#{popupId}]")
   popup = $("##{popupId}")
-  if typeof popup.attr("data-popup-show") is 'undefined'
+  showPopup = (typeof popup.attr("data-popup-show") is 'undefined')
+  # Update popup status and advertise all popup event togglers
+  if showPopup
     popup.attr("data-popup-show", "")
     allEvents.attr("data-popup-shown", "")
   else
     popup.removeAttr("data-popup-show")
     allEvents.removeAttr("data-popup-shown")
+  # Give new popup display status to all listeners
+  listners = popup.data("popup-listner-ids")
+  for listner in listners.split(" ")
+    listnerElement = $("##{listner}")
+    if showPopup
+      listnerElement.attr("data-popup-#{popupId}-shown", "")
+    else
+      listnerElement.removeAttr("data-popup-#{popupId}-shown", "")


refactor type

refactor type

--- lib/atom-ternjs-helper.coffee
+++ lib/atom-ternjs-helper.coffee
@@ -87,9 +87,6 @@
   prepareType: (data) ->
     return unless data.type
     type = data.type.replace(/->/g, ':').replace('<top>', 'window')
-    if !type.endsWith(')') or type.match(/\(\)$/)
-      type = type.replace(/( : .+)/, '')
-    type = type.replace(/^fn\(/, '').replace(/\)$/, '')

   formatTypeCompletion: (obj) ->
     if obj.isKeyword
@@ -103,7 +100,7 @@
     if obj.type is 'string'
       obj.name = obj.name?.replace /(^"|"$)/g, ''

-    obj.type = obj.type?.replace(/->/g, ':').replace('<top>', 'window')
+    obj.type = obj.rightLabel = @prepareType(obj)

     if obj.type.replace(/fn\(.+\)/, '').length is 0
       obj.leftLabel = ''
@@ -113,14 +110,9 @@
       else
         obj.leftLabel = obj.type.replace(/fn\(.{0,}\)/, '').replace(' : ', '')

-    if !obj.type.endsWith(')') or obj.type.match(/\(\)$/)
-      obj.rightLabel = obj.type.replace(/( : .+)/, '')
-    else
-      obj.rightLabel = obj.type
-
     if obj.rightLabel.startsWith('fn')
       if atom.config.get('atom-ternjs.useSnippets')
-        params = @extractParams(obj.rightLabel.replace(/^fn\(/, '').replace(/\)$/, ''))
+        params = @extractParams(obj.rightLabel)
         obj._snippet = @buildSnippet(params, obj.name)
       else
         obj._snippet = "#{obj.name}"
@@ -145,12 +137,15 @@

   extractParams: (type) ->
     return [] unless type
-    start = 0
+    start = type.indexOf('(') + 1
     params = []
     inside = 0
-    for i in [0..type.length - 1]
+    for i in [start..type.length - 1]
+      if type[i] is ':' and inside is -1
+        params.push type.substring(start, i - 2)
+        break
       if i is type.length - 1
-        params.push type.substring(start, i + 1)
+        params.push type.substring(start, i)
         break
       if type[i] is ',' and inside is 0
         params.push type.substring(start, i)


replace all "\'. This serves as a fix if guess returns variables or functions while inside a string. fixes #192

--- lib/atom-ternjs-helper.coffee
+++ lib/atom-ternjs-helper.coffee
@@ -114,11 +114,15 @@

     return obj if !obj.type

+    console.log obj
+
     if !obj.type.startsWith('fn')
       obj._typeSelf = 'variable'

     if obj.type is 'string'
       obj.name = obj.name?.replace /(^"|"$)/g, ''
+    else
+      obj.name = obj.name?.replace /["']/g, ''

     obj.type = obj.rightLabel = @prepareType(obj)

@@ -155,6 +159,7 @@
     suggestionParams = []
     for param, i in params
       param = param.replace '}', '\\}'
+      param = param.replace /'"/g, ''
       suggestionParams.push "#{param}"
     "#{name}(#{suggestionParams.join(',')})"


OX-228 garbage performane test

OX-228 garbage performane test

--- app/assets/javascripts/oxalis/model/binary/cube.coffee
+++ app/assets/javascripts/oxalis/model/binary/cube.coffee
@@ -67,39 +67,6 @@
         (cubeBoundary[2] + 1) >> 1
       ]

-    #@test()
-
-
-  testSetSubBuckets : ([bucket_x, bucket_y, bucket_z, zoomStep]) ->
-
-    return if zoomStep == -1
-
-    for dx in [0..1]
-      for dy in [0..1]
-        for dz in [0..1]
-          @requestBucketByZoomedAddress([(bucket_x << 1) + dx, (bucket_y << 1) + dy, (bucket_z << 1) + dz, zoomStep - 1])
-          @setBucketByZoomedAddress([(bucket_x << 1) + dx, (bucket_y << 1) + dy, (bucket_z << 1) + dz, zoomStep - 1], 'DATA')
-
-
-  testSetAllSubBuckets : ([bucket_x, bucket_y, bucket_z, zoomStep]) ->
-
-    return if zoomStep == -1
-
-    for dx in [0..1]
-      for dy in [0..1]
-        for dz in [0..1]
-          @setBucketByZoomedAddress([(bucket_x << 1) + dx, (bucket_y << 1) + dy, (bucket_z << 1) + dz, zoomStep - 1], 'DATA')
-          @testSetAllSubBuckets([(bucket_x << 1) + dx, (bucket_y << 1) + dy, (bucket_z << 1) + dz, zoomStep - 1])
-
-
-  test : () ->
-
-    bucket = [10,10,10,1]
-    @testSetSubBuckets(bucket)
-    console.log @getBucketByZoomedAddress(bucket)
-    @tryCollectBucket([20,20,20,0])
-    console.log @getBucketByZoomedAddress(bucket)
-

   getBucketIndexByZoomedAddress : ([bucket_x, bucket_y, bucket_z, zoomStep]) ->

@@ -136,7 +103,7 @@

     if bucket?

-      @access.push(address)
+      @access.unshift(address)
       bucket.access++
       bucket.data

@@ -191,7 +158,7 @@
     if bucketData?

       @bucketCount++
-      @access.push([bucket_x, bucket_y, bucket_z, zoomStep])
+      @access.unshift([bucket_x, bucket_y, bucket_z, zoomStep])

       bucket.access++
       bucket.data = bucketData
@@ -293,7 +260,7 @@

     while @bucketCount > @MAXIMUM_BUCKET_COUNT and @access.length

-      @tryCollectBucket(@access.shift())
+      @tryCollectBucket(@access.pop())

   positionToZoomedAddress : ([x, y, z], zoomStep) ->


Remove placeholder more quickly

Remove placeholder more quickly

--- app/assets/javascripts/views/paper_edit_view.js.coffee
+++ app/assets/javascripts/views/paper_edit_view.js.coffee
@@ -10,7 +10,13 @@

   bindPlaceholderEvent: ->
     $('.editable').on "keyup", "div[contenteditable]", (e) =>
-      Ember.run.debounce(@, @updatePlaceholder, 1000)
+
+      # if we're currently showing placeholder we want it to go away
+      # when the user starts typing, without delay
+      if @get('controller.showPlaceholder')
+        @updatePlaceholder()
+      else
+        Ember.run.debounce(@, @updatePlaceholder, 1000)

   updatePlaceholder: ->
     @set('controller.showPlaceholder', @get('visualEditor.isEmpty'))


Added more detailed error messages for sessions.

--- app/assets/javascripts/views/sessions/dialog.js.coffee
+++ app/assets/javascripts/views/sessions/dialog.js.coffee
@@ -69,7 +69,7 @@
       when "register"         then @toggleRegister()
       when "login"            then @toggleLogin()
       when "complete"         then @toggleComplete()
-
+
     false

@@ -125,9 +125,15 @@
       when "register"         then @submitRegister()
       when "login"            then @submitLogin()
       when "complete"         then @submitComplete()
-
+
     e.preventDefault()
     false
+
+  buildErrors: (errors) ->
+    t = "
"
+    $.each errors, (index,error) ->
+      t += "
#{error.ref_node} #{error.message}
" if error.type == "field"
+    t += "
"

   submitRestore: ->

@@ -173,20 +179,22 @@
         @hide(logout: false)
       error: (response) =>
         resp = $.parseJSON(response.responseText)
-        @.$('.input-group').tooltip(
-          placement: 'top'
-          trigger: 'manual'
-          animation: false
-          title: resp.flash.title + " " + resp.flash.message
-        ).tooltip('show')

         switch resp.status
-          # when 401
-          #   # do stuff
+          when 422
+            @.$('.input-group').tooltip(
+              placement: 'top'
+              trigger: 'manual'
+              animation: false
+              html: true
+              title: @buildErrors(resp.errors)
+
+            ).tooltip('show')
           when 403
             @logoutUser()
-          # when 500
-          #   # do stuff
+          else
+
+

   submitLogin: ->

@@ -207,20 +215,17 @@
         @hide(logout: false)
       error: (response) =>
         resp = $.parseJSON(response.responseText)
-        @.$('.input-group').tooltip(
-          placement: 'top'
-          trigger: 'manual'
-          animation: false
-          title: resp.flash.title + " " + resp.flash.message
-        ).tooltip('show')

         switch resp.status
-          # when 401
-          #   # do stuff
+          when 401
+            @.$('.input-group').tooltip(
+              placement: 'top'
+              trigger: 'manual'
+              animation: false
+              title: resp.flash.title + " " + resp.flash.message
+            ).tooltip('show')
           when 403
             @logoutUser()
-          # when 500
-          #   # do stuff

   submitComplete: ->
     submitData = {}
@@ -241,16 +246,17 @@
         @hide(logout: false)
       error: (response) =>
         resp = $.parseJSON(response.responseText)
-        @.$('.input-group').tooltip(
-          placement: 'top'
-          trigger: 'manual'
-          animation: false
-          title: resp.flash.title + " " + resp.flash.message
-        ).tooltip('show')

         switch resp.status
-          # when 422
-          #   # do stuff
+          when 422
+            @.$('.input-group').tooltip(
+              placement: 'top'
+              trigger: 'manual'
+              animation: false
+              html: true
+              title: @buildErrors(resp.errors)
+
+            ).tooltip('show')
           when 403
             @logoutUser()
           # when 500


fix: clear alert errors on entity reload

--- src/modules/siren-browser/controllers/showCtrl.coffee
+++ src/modules/siren-browser/controllers/showCtrl.coffee
@@ -2,6 +2,8 @@
   require "../siren-browser.coffee"
   require "./actionModalCtrl.coffee"
   showCtrl = ($scope, $stateParams, $rootScope, sirenEntity, $modal) ->
+    # Reset Alerts
+    $rootScope.alerts = []
     @url = $stateParams.url
     @actions = sirenEntity.data.actions
     @class = sirenEntity.data.class


remove some code

remove some code

--- gulp/tasks/watch.coffee
+++ gulp/tasks/watch.coffee
@@ -2,8 +2,6 @@
 gulp = require 'gulp'

 gulp.task 'watch', ['server'], ->
-#  gulp.watch config.scripts.src, ['browserify']
   gulp.watch config.styles.src, ['styles']
   gulp.watch config.images.src, ['images', 'reload']
-  gulp.watch config.views.watch, ['views']
-#  gulp.watch config.coffee.src, ['coffee']+  gulp.watch config.views.watch, ['views']


Add a close box

Add a close box

--- app/controllers/popup_button.coffee
+++ app/controllers/popup_button.coffee
@@ -1,7 +1,5 @@
 attach = require 'zootorial/lib/attach'
 $ = require 'jqueryify'
-
-log = -> console?.log? arguments...

 class PopupButton
   @fromDOM: (el) ->
@@ -25,22 +23,26 @@

   classDelay: 0

+  eventId: NaN
+
   constructor: (params = {}) ->
     @[key] = value for own key, value of params when key of @
-    log params
+    @eventId = Math.random().toString(36)[2...]

     @el.on 'click', @onClick

     @popup.addClass 'hidden'
     @popup.remove()
+    @popup.prepend '<button name="close">×</button>'

-    $(document).on 'click', @onDocumentClick
-
-  onClick: (e) =>
+  onClick: =>
     @toggle()

+  onClickClose: =>
+    @close()
+
   onDocumentClick: ({target}) =>
-    @close() unless $(target).parents().andSelf().is @el
+    @close() unless $(target).parents().andSelf().is @el.add @popup

   toggle: ->
     if @el.hasClass 'open' then @close() else @open()
@@ -51,9 +53,15 @@
     attach @popup, @attachAt.split(/\s/), @el, @attachTo.split(/\s/), margin: @attachMargin
     setTimeout $.proxy(@popup, 'removeClass', 'hidden'), @classDelay

+    @popup.on 'click', 'button[name="close"]', @onClickClose
+    $(document).on "click.#{@eventId}", @onDocumentClick
+
   close: ->
     @el.removeClass 'open'
     @popup.addClass 'hidden'
     setTimeout $.proxy(@popup, 'remove'), @classDelay

+    @popup.off 'click'
+    $(document).off "click.#{@eventId}"
+
 module.exports = PopupButton


Add test for Unsupported type

Add test for Unsupported type

--- test/src/backbone.websql.deferred-test.coffee
+++ test/src/backbone.websql.deferred-test.coffee
@@ -88,6 +88,12 @@
         Backbone.WebSQL.prototype._executeSql.calledWith("CREATE TABLE IF NOT EXISTS `klass` (`id` unique, `value`, `user_id`, `parent_id`);")
       ).to.be.true

+    it 'should throw an error if unknown type is passed', ->
+      fn = -> Klass = Backbone.Model.extend
+        store: new Backbone.WebSQL(db, 'klass', [{name: 'user_id'}, {name: 'parent_id', type: 'foo'}])
+
+      expect(fn).to.throw('Unsupported type: foo')
+
   describe '.create', ->
     context 'when optional column doesnt exist', ->
       beforeEach (done) ->


Kill example heading

Kill example heading

--- src/editor.coffee
+++ src/editor.coffee
@@ -27,7 +27,6 @@
 # be called with all current editor instances and also when any editor is
 # created in the future.
 #
-# ## Example
 # ```coffeescript
 #   atom.workspace.eachEditor (editor) ->
 #     editor.insertText('Hello World')


save old dom value to allow a muted function to call another muted function

--- src/editor.coffee
+++ src/editor.coffee
@@ -121,6 +121,7 @@
     setInterval(checkSelectionChange, this.options.POLL_INTERVAL)

   insertAt: (startIndex, text, attributes = {}) ->
+    oldIgnoreDomChange = @ignoreDomChanges
     @ignoreDomChanges = true
     range = if _.isNumber(startIndex) then new Tandem.Range(this, startIndex, startIndex) else startIndex
     this.preserveSelection(range.start, text.length, =>
@@ -134,7 +135,7 @@
         range = null
       )
     )
-    @ignoreDomChanges = false
+    @ignoreDomChanges = oldIgnoreDomChange

   insertNewlineAt: (startIndex) ->
     range = new Tandem.Range(this, startIndex, startIndex)
@@ -147,7 +148,8 @@
       div.appendChild(node)
       node = nextSibling

-  deleteAt: (startIndex, length) ->
+  deleteAt: (startIndex, length) ->
+    oldIgnoreDomChange = @ignoreDomChanges
     @ignoreDomChanges = true
     range = if _.isNumber(startIndex) then new Tandem.Range(this, startIndex, startIndex + length) else startIndex
     this.preserveSelection(range.start, 0 - length, =>
@@ -177,7 +179,7 @@
       if lines.length == 2
         Tandem.Utils.Node.combineLines(lines[0], lines[1])
     )
-    @ignoreDomChanges = false
+    @ignoreDomChanges = oldIgnoreDomChange

   getAt: (startIndex, length) ->
     # - Returns array of {text: "", attr: {}}
@@ -193,14 +195,16 @@
   # applyAttribute: (TandemRange range, Object attribute) ->
   # applyAttribute: (Number startIndex, Number length, Object attribute) ->
   applyAttribute: (startIndex, length, attributes, emitEvent = true) ->
+    oldIgnoreDomChange = @ignoreDomChanges
     @ignoreDomChanges = true
     if !_.isNumber(startIndex)
       [range, attributes] = [startIndex, length]
       startIndex = range.start.getIndex()
       length = range.end.getIndex() - startIndex
+    else
+      range = new Tandem.Range(this, startIndex, startIndex + length)
     this.preserveSelection(range.start, 0, =>
       for attr,value of attributes
-        range = new Tandem.Range(this, startIndex, startIndex + length) unless range?
         range.splitEnds()
         _.each(range.groupNodesByLine(), (nodes) =>
           return if nodes.length == 0
@@ -238,7 +242,7 @@
       deltas.push(new JetRetain(startIndex + length, docLength)) if startIndex + length < docLength
       delta = new JetDelta(docLength, docLength, deltas)
       this.emit(this.events.API_TEXT_CHANGE, delta)
-    @ignoreDomChanges = false
+    @ignoreDomChanges = oldIgnoreDomChange

   preserveSelection: (modificationStart, charAdditions, fn) ->
     currentSelection = this.getSelection()


views/partials automatically loads new partials

--- www/src/views/partials.coffee
+++ www/src/views/partials.coffee
@@ -1,10 +1,17 @@
-define ["underscore", "markdown", "settings", "text!templates/partials.html"], (_, markdown, settings, html) ->
+define [
+    "underscore", "markdown"
+    "settings"
+    "text!templates/partials.html"
+], (_, markdown, settings, html) ->
     el = $(html)
-    user: _.template(el.find("script.user").text())
-    quest_labels: _.template(el.find("script.quest-labels").text())
-    edit_tools: _.template(el.find("script.edit-tools").text())
-    quest_link: _.template(el.find("script.quest-link").text())
-    watchers: _.template(el.find("script.watchers").text())
-    markdown: markdown
-    settings: settings

+    result =
+        markdown: markdown
+        settings: settings
+
+    el.find("script").each ->
+        name = @.className
+        name = name.replace /-/g, "_"
+        result[name] = _.template $(@).text()
+
+    result


Fix spelling in bundled examples

Fix spelling in bundled examples

--- example_observables.coffee
+++ example_observables.coffee
@@ -1,7 +1,7 @@
 module.exports = [
   {
     name: 'simpleMap'
-    description: 'The squares of a few values over time'
+    description: 'The squares of a few values over time.'
     observable:
       root:
         type: 'fromTime'
@@ -15,7 +15,7 @@
   }
   {
     name: 'mapAndTake'
-    description: 'The squares of infinite values bounded by take'
+    description: 'The squares of infinite values bounded by take.'
     observable:
       root:
         type: 'interval', args: ["500"]
@@ -27,7 +27,7 @@
   {
     name: "raceCondition#1"
     description:
-      "User requests page 1, changes his mind and requests page 2, receives wrong page"
+      "User requests page 1, changes his mind and requests page 2, receives wrong page."
     observable:
       root:
         type: 'fromTime', args: ["{1000: 1, 2000: 2}"]
@@ -50,7 +50,7 @@
   }
   {
     name: "fixedRaceCondition#1"
-    description: "Fix race condition #1 by mapping waiting for previous response before new request. The user gets the every requested page and has to wait"
+    description: "Fix race condition #1 by mapping waiting for previous response before new request. The user gets the every requested page and has to wait."
     observable:
       root:
         type: 'fromTime', args: ["{1000: 1, 2000: 2}"]
@@ -73,7 +73,7 @@
   }
   {
     name: "fixedRaceCondition#2"
-    description: "Fix race condition #1 by flat mapping the latest request. The user gets the latest requested page immediately"
+    description: "Fix race condition #1 by flat mapping the latest request. The user gets the latest requested page immediately."
     observable:
       root:
         type: 'fromTime', args: ["{1000: 1, 2000: 2}"]
@@ -119,7 +119,7 @@
   }
   {
     name: 'delayWithSelector'
-    description: "Every value takes progressively longer to process"
+    description: "Every value takes progressively longer to process."
     observable:
       root:
         type: 'interval', args: ["500"]
@@ -149,7 +149,7 @@
   }
   {
     name: 'generateAndBuffer'
-    description: "Values come later and later, buffer length changes"
+    description: "Values come later and later, buffer length changes."
     observable:
       root:
         type: 'generateWithRelativeTime',
@@ -169,7 +169,7 @@
   }
   {
     name: 'merge'
-    description: 'Merge two unrelated observables'
+    description: 'Merge two unrelated observables.'
     observable:
       root:
         type: 'timer'


added clear attrAccessor

added clear attrAccessor

--- src/engine.coffee
+++ src/engine.coffee
@@ -5,6 +5,7 @@
     ambientLight: 1
     backgroundColor: "#00010D"
     cameraTransform: Matrix.IDENTITY
+    clear: false
     excludedModules: []
     includedModules: []
     paused: false


moving clear back inside of camera transform

--- src/engine.coffee
+++ src/engine.coffee
@@ -135,12 +135,12 @@
       self.trigger "afterUpdate"

     draw = ->
-      if I.clear
-        canvas.clear()
-      else if I.backgroundColor
-        canvas.fill(I.backgroundColor)
-
-      I.canvas.withTransform I.cameraTransform, (canvas) ->
+      I.canvas.withTransform I.cameraTransform, (canvas) ->
+        if I.clear
+          canvas.clear()
+        else if I.backgroundColor
+          canvas.fill(I.backgroundColor)
+
         self.trigger "beforeDraw", canvas

         if I.zSort


'super' means different things in instance/static methods.

--- lib/closure-conversion.coffee
+++ lib/closure-conversion.coffee
@@ -133,17 +133,31 @@
                 @class_stack = new Stack
                 @method_stack = new Stack

+        createSuperReference = (is_static, id) ->
+                if is_static
+                        obj = superid
+                else
+                        obj = {
+                                type: MemberExpression
+                                object: superid
+                                property: create_identifier "prototype"
+                                computed: false
+                        }
+
+                if not id?
+                        return obj
+
+                return {
+                        type: MemberExpression
+                        object: obj
+                        property: id
+                        computed: false
+                }
+
+
         visitCallExpression: (n) ->
                 if n.callee.type is Identifier and n.callee.name is "super"
-                        n.callee =
-                                type: MemberExpression
-                                object:
-                                        type: MemberExpression
-                                        object: superid
-                                        property: create_identifier "prototype"
-                                        computed: false
-                                property: @method_stack.top
-                                computed: false
+                        n.callee = createSuperReference @method_stack.top.static, @method_stack.top.key
                 else
                         n.callee = @visit n.callee
                 n.arguments = @visitArray n.arguments
@@ -155,7 +169,8 @@
                 n

         visitIdentifier: (n) ->
-                return superid if n.name is "super"
+                if n.name is "super"
+                        return createSuperReference @method_stack.top.static
                 n

         visitClassDeclaration: (n) ->
@@ -164,7 +179,7 @@

                 # XXX this push/pop should really be handled in @visitMethodDefinition
                 for class_element in n.body.body
-                        @method_stack.push class_element.key
+                        @method_stack.push class_element
                         class_element.value = @visit class_element.value
                         @method_stack.pop()


allowing default key events in input elements

--- src/engine.coffee
+++ src/engine.coffee
@@ -14,7 +14,7 @@
   # Prevent browser contextmenu from popping up in games.
   document.oncontextmenu = -> false
   $(document).bind "keydown", (event) ->
-    event.preventDefault()
+    event.preventDefault() unless $(event.target).is "input"

   ###*
   The Engine controls the game world and manages game state. Once you


Added explicit calls to super for all error classes

--- src/errors.coffee
+++ src/errors.coffee
@@ -27,14 +27,25 @@
 class AuthKeyError extends CogsError
   constructor: (message, cause) -> super message, cause

+class ApiError extends CogsError
+  constructor: (message, cause, statusCode, details) ->
+    super message, cause, statuscode, details
+
+class InfoError extends CogsError
+  constructor: (message, cause, statusCode, details) ->
+    super message, cause, statuscode, details
+
+class ToolsError extends CogsError
+  constructor: (message, cause, statusCode, details) ->
+    super message, cause, statuscode, details
+
+class PubSubError extends CogsError
+  constructor: (message, cause, statusCode, details) ->
+    super message, cause, statuscode, details
+
 class PubSubFailureResponse extends PubSubError
   constructor: (message, cause, statusCode, details, @record) ->
     super message, cause, statuscode, details
-
-class ApiError extends CogsError
-class InfoError extends CogsError
-class PubSubError extends CogsError
-class ToolsError extends CogsError

 module.exports =
   ApiError: ApiError


remove logs

remove logs

--- lib/controllerProvider.coffee
+++ lib/controllerProvider.coffee
@@ -45,14 +45,12 @@
         controllerPath = path.join(alloyRootPath,'controllers');
         if util.isExistAsDirectory(controllerPath)
           files = find.fileSync /\.js$/, controllerPath
-          console.log files
           for file in files
             # if currentPath != file # exclude current controller
             completions.push
               text: file.replace(controllerPath+path.sep,'').split('.')[0]
               type: 'require',
               replacementPrefix : util.getCustomPrefix(request)
-      console.log completions
       return completions
   }
   {


Improve time slider boundaries

Improve time slider boundaries

--- assets/javascripts/react/components/time_slider.coffee
+++ assets/javascripts/react/components/time_slider.coffee
@@ -23,8 +23,8 @@
     [min, max] = @getMinAndMax(getUniqueTimes(@props.notifications))

     <Slider id="time_slider"
-      min={Math.max(min - 100, 0)}
-      max={max + 100}
+      min={Math.round(min * 0.1)}
+      max={Math.round(max * 1.1)}
       step=1
       value={@props.value}
       style={width: "100%"}


Use type for id, prevent duplicate notices from showing

--- shared/src/model/notifications.coffee
+++ shared/src/model/notifications.coffee
@@ -30,7 +30,8 @@

   display: (notice) ->
     # fill in an id and type if not provided
-    NOTICES.push( defaults(clone(notice), id: uniqueId(CLIENT_ID), type: CLIENT_ID ))
+    notice = defaults(clone(notice), id: uniqueId(CLIENT_ID), type: CLIENT_ID)
+    NOTICES.push(notice) unless find(NOTICES, id: notice.id)
     @emit('change')

   _startPolling: (type, url) ->
@@ -78,9 +79,11 @@
     return if isEmpty(role) or role.type is 'teacher'
     studentId = find(course.students, role_id: role.id)?.student_identifier
     if isEmpty(studentId) and moment().diff(role.joined_at, 'days') > 7
-      @display({type: @POLLING_TYPES.MISSING_STUDENT_ID, course, role})
+      id = @POLLING_TYPES.MISSING_STUDENT_ID
+      @display({id, type: id, course, role})
     if moment(course.ends_at).isBefore(moment(), 'day')
-      @display({type: @POLLING_TYPES.COURSE_HAS_ENDED, course, role})
+      id = @POLLING_TYPES.COURSE_HAS_ENDED
+      @display({id,  type: id, course, role})

 }


Use path.extname() instead of fsUtils.extension()

--- lib/image-edit-session.coffee
+++ lib/image-edit-session.coffee
@@ -14,7 +14,7 @@
     imageExtensions = ['.gif', '.jpeg', '.jpg', '.png']
     Project = require 'project'
     Project.registerOpener (filePath) ->
-      if _.include(imageExtensions, fsUtils.extension(filePath))
+      if _.include(imageExtensions, path.extname(filePath))
         new ImageEditSession(filePath)

   @deserialize: ({path}={}) ->


Adding resample to BokehJS

Adding resample to BokehJS

--- bokehjs/src/coffee/source/resample.coffee
+++ bokehjs/src/coffee/source/resample.coffee
@@ -7,6 +7,10 @@
   class Resample extends ServerDataSource
     type: 'Resample'

+    initialize : (attrs, options) =>
+      super(attrs, options)
+      @callbacks = {}
+
   class Resamples extends Backbone.Collection
     model: Resample
   return {


Even betterer accept-ranges logic... :P

Even betterer accept-ranges logic... :P

--- src/getbot.coffee
+++ src/getbot.coffee
@@ -28,7 +28,7 @@
       if !error
         switch response.statusCode
           when 200
-            if response.headers['accept-ranges'] is "none" or response.headers['accept-ranges'] is null
+            if response.headers['accept-ranges'] and !response.headers['accept-ranges'] is "bytes"
               opts.connections = 1
             @fileSize = response.headers['content-length']


Do not send "Authorization" header to S3 URL.

--- src/github.coffee
+++ src/github.coffee
@@ -57,17 +57,19 @@

   # Private: Get the options for downloading asset.
   getDownloadOptions: (url) ->
+    isGitHubUrl = require('url').parse(url).hostname is 'api.github.com'
+
     # Only set headers for GitHub host, the url could also be a S3 link and
     # setting headers for it would make the request fail.
     headers =
-      if require('url').parse(url).hostname is 'api.github.com'
+      if isGitHubUrl
         accept: 'application/octet-stream'
         'user-agent': 'node-github-releases/0.1.0'
       else
         {}

     # Set access token.
-    headers.authorization = "token #{@token}" if @token?
+    headers.authorization = "token #{@token}" if isGitHubUrl and @token?

     options =
       url: url


Update hoc property in schema for signup modal

--- app/schemas/models/user.ozar.coffee
+++ app/schemas/models/user.ozar.coffee
@@ -131,7 +131,7 @@
   hourOfCode: {type: 'boolean'}
   hourOfCodeComplete: {type: 'boolean'}
   hourOfCodeOptions: c.object({title: 'Options useful for hour of code users'}, {
-    completeSignupModalSeen: {type: 'boolean', description: 'Used to determine if hoc teachers saw the complete signup modal on dashboard  - only valid for teachers who signup from hoc signup flow'}
+    showCompleteSignupModal: {type: 'boolean', description: 'Whether to show complete signup modal on teacher dashboard - only valid for teachers who signup from hoc signup flow'}
   })
   createdOnHost: { type: 'string' }


One log line per request.

One log line per request.

--- server/code/index.coffee
+++ server/code/index.coffee
@@ -79,6 +79,11 @@
       # CSV file is:
       # app,unique-request-id,route,URL,milliseconds
       line = "custard,#{unique},#{name},#{req.url},#{duration}\n"
+
+      # The first time .send() is called we write a line to the
+      # log. Possibly it would be better to use the last time
+      # .send() is called, but this is way simpler.
+      res.send = oldSend
       if requestStream
         requestStream.write line, () ->
           oldSend.apply res, args


identd authentication

identd authentication

--- server/code/index.coffee
+++ server/code/index.coffee
@@ -1,3 +1,4 @@
+net = require 'net'
 fs = require 'fs'
 path = require 'path'
 existsSync = fs.existsSync || path.existsSync
@@ -117,6 +118,24 @@
 app.set 'view engine', 'html'
 js.root = 'code'

+checkIdent = (req, resp, next) ->
+  remotePort = req.headers['x-real-port'] or req.connection.remotePort
+  localPort = req.headers['x-server-port']
+  socket = net.connect
+    port: 113
+    host: req.headers['x-server-ip']
+  , ->
+    socket.write "#{remotePort},#{localPort}\r\n"
+  data = ''
+  socket.on 'error', (err) ->
+    next()
+  socket.on 'data', (buffer) ->
+    data += buffer.toString()
+  socket.on 'end', ->
+    box = data.split(':')[3]
+    req.ident = box.trim()
+    next()
+
 # Middleware (for checking users)
 checkUserRights = (req, resp, next) ->
   console.log 'CheckUserRights', req.method, req.url, req.user.effective.shortName, req.params.user


added dynamic routes to metrics endpoints

--- src/koaApi.coffee
+++ src/koaApi.coffee
@@ -17,6 +17,8 @@
 metrics = require './api/metrics'
 serverRestart = require './restart'
 statsd = require './model/statsd'
+config = require './config/config'
+statsd_instance = config.get 'statsd'

 exports.setupApp = (done) ->

@@ -77,9 +79,10 @@
   app.use route.get '/visualizer/events/:receivedTime', visualizer.getLatestEvents
   app.use route.get '/visualizer/sync', visualizer.sync

-  app.use route.get '/metrics', metrics.getGlobalLoadTimeMetrics
-  app.use route.get '/metrics/status', metrics.getGlobalStatusMetrics
-  app.use route.get '/metrics/:type/:channelId', metrics.getChannelMetrics
+  app.use route.get '/metrics', if statsd_instance.enabled then statsd.retrieveTransactionCountPerHour else metrics.getGlobalLoadTimeMetrics
+  app.use route.get '/metrics/status', if statsd_instance.enabled then statsd.fetcGlobalStatusMetrics else metrics.getGlobalStatusMetrics
+  app.use route.get '/metrics/:type/:channelId', if statsd_instance.enabled then statsd.retrieveChannelMetrics metrics.getChannelMetrics
+  app.use route.get '/metrics/load-time', if statsd_instance.enabled then statsd.retrieveAverageLoadTimePerHour else metrics.getGlobalLoadTimeMetrics

   app.use route.get '/statsd/status', statsd.fetcGlobalStatusMetrics
   app.use route.get '/statsd', statsd.retrieveTransactionCountPerHour


Add support for DB scoping in mongo compount

--- library/membrane/mongo.coffee
+++ library/membrane/mongo.coffee
@@ -64,7 +64,8 @@
     # Please be sure invoke the `next` arg to proceed, if relevant.
     unregister: (kernel, router, next) ->
         return next() unless _.isObject kernel.mongo
-        {host, port, options} = kernel.mongo._db.serverConfig
+        database = kernel.mongo._db or kernel.mongo
+        {host, port, options} = database.serverConfig
         message = "Disconnecting from Mongo at %s:%s"
         logger.info message.cyan.underline, host, port
         kernel.mongo.close(); delete kernel.mongo; next()
@@ -89,6 +90,10 @@
         kernel.mongo.open (error, client) ->
             assert.ifError error, "failed to connect: #{error}"
             assert.ok _.isObject kernel.mongo = client
+            scope = _.isString database = config.database
+            message = "Setting the MongoDB database: %s"
+            kernel.mongo = client.db database if scope
+            logger.info message.magenta, database if scope
             return next()

     # A hook that will be called prior to instantiating the service


Stuff in some more asserts in the kernel, per coding convs

--- library/nucleus/kernel.coffee
+++ library/nucleus/kernel.coffee
@@ -89,10 +89,10 @@
         decor = (o) -> (a...) -> level o.explain; o.routine a...
         run = arguments.length is 0 and _.isArray @$configure
         return async.series _.map @$configure, decor if run
-        return if not @$configure and not arguments.length
+        return yes if not @$configure and not arguments.length
         assert _.isFunction(routine), "invalid config routine"
         assert _.isString(explain), "no explanation given"
-        assert (@$configure ?= []).push
+        assert (@$configure ?= []).push new Object
             explain: explain
             routine: routine

@@ -108,13 +108,13 @@
         sigterm = "Received the SIGTERM (terminate signal)"
         process.on "SIGINT", => @shutdownKernel sigint
         process.on "SIGTERM", => @shutdownKernel sigterm
-        assert @setupRoutableServices()
-        assert @setupConnectPipeline()
-        assert @setupListeningServers()
-        assert @setupSocketServers()
-        assert @setupHotloadWatcher()
-        @constructor.configure()
-        logger.info message.red
+        assert not _.isEmpty @setupRoutableServices()
+        assert not _.isEmpty @setupConnectPipeline()
+        assert not _.isEmpty @setupListeningServers()
+        assert not _.isEmpty @setupSocketServers()
+        assert not _.isEmpty @setupHotloadWatcher()
+        assert @constructor.configure()
+        logger.info message.red; this

     # The public constructor of the kernel instrances. Generally
     # you should neither use it directly, not override. It serves
@@ -292,3 +292,4 @@
         @router = new routing.Router this
         assert @middleware = @router.middleware
         @middleware = @middleware.bind @router
+        assert _.isFunction @middleware; this


remove the constant duplication

remove the constant duplication

--- library/nucleus/scaled.coffee
+++ library/nucleus/scaled.coffee
@@ -54,13 +54,6 @@
 # library that allows for effective proxying of the HTTP requests.
 # Normally this kernel should always be preferred over Generic one!
 module.exports.ScaledKernel = class ScaledKernel extends GraniteKernel
-
-    # This sets up the default identica for this kernel. It forms
-    # an identica of a certain recommended format and populates it
-    # with data takes from the `FRAMEWORK` definition in `Generic`
-    # kernel. Refer to that kernel and to `identica` method there
-    # for more information on semantics and the way of working it.
-    @identica -> "#{@APPLICATION.name}@#{@APPLICATION.version}"

     # Prepare and setup an HTTPS master server. This server is the
     # proxy server that is going to consume all the HTTPS requests


Set cache control headers, support different image extensions

--- server.coffee
+++ server.coffee
@@ -1,86 +1,97 @@
-http = require 'http'
-util = require 'util'
-url  = require 'url'
-gm   = require 'gm'
+http    = require 'http'
+url     = require 'url'
+gm      = require 'gm'
+path    = require 'path'
+coffee  = require 'coffee-script'

 config = require './config'

-server = http.createServer((req, res) ->
-  try
-    console.log("Incoming Request from: #{req.connection.remoteAddress} for href: #{req.url}")
+class Route
+  constructor: (pathname) ->
+    @pathComponents = pathname.split('/')
+    @pathComponents.shift() if @pathComponents[0] == ''

-    # pipe some details to the node console
-    urlObj = url.parse(req.url)
+    @namespace = @pathComponents.shift()

-    pathComponents = urlObj.pathname.split('/')
-    pathComponents.shift() if pathComponents[0] == ''
+    lastPathComponent = @pathComponents.pop()
+    ext = path.extname(lastPathComponent)
+    @pathComponents.push(path.basename(lastPathComponent, ext))
+    @extension = ext.substring(1, ext.length)

-    nsConfig = config.namespaces[pathComponents.shift()]
-    if nsConfig
-      targetURL = nsConfig.urlTemplate
-      fileName = null
-      index = 0
-      while pathComponents.length
-        comp = pathComponents.shift()
-        if pathComponents.length == 0
-          fileName = comp
-          idx = comp.lastIndexOf('.')
-          comp = comp.substring(0, idx) if idx >= 0
-        targetURL = targetURL.replace(new RegExp("\\$#{index + 1}", 'g'), comp)
-        index++
+class Processor
+  constructor: (@ns, @request, @response) ->

-      console.log("Fetching image #{targetURL}")
-      http.get(url.parse(targetURL), (imageRes) ->
-        console.log('HEADERS: ' + JSON.stringify(imageRes.headers))
+  fetch: (urlString) ->
+    http.get(url.parse(urlString), (imageResponse) =>
+      console.log('HEADERS: ' + JSON.stringify(imageResponse.headers))

-        if imageRes.statusCode == 200
-          image = gm(imageRes, fileName)
-          nsConfig.processImage(image)
+      if imageResponse.statusCode == 200
+        image = gm(imageResponse, path.basename(urlString))
+        @ns.processImage(image)
+        image.stream((err, stdout, stderr) =>
+          if err
+            console.log("[ERR] error processing image: #{err.message}")
+            this.fail(500, err.message)

-          image.stream((err, stdout, stderr) ->
-            if err
-              console.log("processing error: #{err}")
-              res.writeHead(500, {
-                'Date': new Date().toUTCString(),
-                'Content-Type': 'text/plain'
-              })
-              res.end(err.message)
+          else
+            headers = coffee.helpers.extend({'Cache-Control': 'public'}, imageResponse.headers)
+            @response.writeHead(imageResponse.statusCode, headers)
+            stdout.pipe(@response)
+        )

-            else
-              res.writeHead(imageRes.statusCode,
-                'Date': imageRes.headers['date'],
-                'Last-Modified': imageRes.headers['last-modified'],
-                'ETag': imageRes.headers['etag'],
-                'Content-Type': 'image/jpeg'
-              )
-              stdout.pipe(res))
+      else
+        headers = coffee.helpers.extend({'Cache-Control': 'no-cache'}, imageResponse.headers)
+        @response.writeHead(imageResponse.statusCode, headers)
+        imageResponse.pipe(@response)
+
+    ).on('error', (err) =>
+      console.log("[ERR] error fetching image: #{err.message}")
+      this.fail(500, err.message)
+    )
+
+  fail: (statusCode, message) ->
+    @response.writeHead(statusCode,
+      'Date': new Date().toUTCString()
+      'Content-Type': 'text/plain'
+      'Cache-Control': 'no-cache'
+    )
+    @response.end(err.message)
+
+
+class Emma
+  constructor: ->
+    @server = http.createServer((req, res) =>
+      try
+        console.log("Incoming Request from: #{req.connection.remoteAddress} for href: #{req.url}")
+        route = new Route(url.parse(req.url).pathname)
+
+        if ns = config.namespaces[route.namespace]
+          targetURL = ns.urlTemplate
+
+          for comp, i in route.pathComponents
+            targetURL = targetURL.replace(new RegExp("\\$#{i + 1}", 'g'), comp)
+
+          targetURL = targetURL.replace(/\$extension/g, route.extension)
+
+          console.log("Fetching image #{targetURL}")
+          new Processor(ns, req, res).fetch(targetURL)

         else
-          # res.setEncoding('utf8')
-          imageRes.on('data'  , (chunk) -> res.write(chunk))
-          imageRes.on('end'   , -> res.end())
-          imageRes.on('close' , -> res.end())
+          res.writeHead(404,
+            'Cache-Control': 'no-cache'
+          )
+          res.end('Not Found')

-      ).on('error', (err) ->
-        console.log("problem with request: #{err.message}")
-        res.writeHead(500)
-        res.end(err.message, {
-          'Date': new Date().toUTCString(),
-          'Content-Type': 'text/plain'
-        })
-      )
+      catch err
+        console.log("[ERR] #{err.message}")
+        res.writeHead(500,
+          'Cache-Control': 'no-cache'
+        )
+        res.end('Internal Server Error')
+    )
+  start: (host, port) ->
+    @server.listen(port, host, -> console.log("Server running at http://#{host}:#{port}/"))

-    else
-      res.writeHead(404)
-      res.end('Not Found')
+exports.server = new Emma()
+exports.server.start('0.0.0.0', 1337)

-  catch err
-    # handle errors gracefully
-    util.puts(err)
-    res.writeHead(500)
-    res.end('Internal Server Error')
-)
-
-server.listen(1337, '0.0.0.0', ->
-  console.log('Server running at http://0.0.0.0:1337/')
-)


Rename `template` to `templateHTML`

Rename `template` to `templateHTML`

--- server.coffee
+++ server.coffee
@@ -4,7 +4,7 @@

 operations = require './lib/operations'

-template = (message, subtitle) -> '
+templateHTML = (message, subtitle) -> '
 <html>
   <head>
     <title>Fuck Off As A Service (FOAAS)</title>
@@ -31,7 +31,7 @@
     "application/json": ->
       res.send JSON.stringify { message: message, subtitle: subtitle }
     "text/html": ->
-      res.send template(message,subtitle)
+      res.send templateHTML(message,subtitle)

 app = express()
 app.use(express.bodyParser())


<server> Adapter launcher.

<server> Adapter launcher.

--- server.coffee
+++ server.coffee
@@ -1,4 +1,26 @@
+###
+# =================
+# LAUNCHER - Server
+# =================
+# Launches the application.
+###
+
 americano = require 'americano'
+
+module.exports.start = start = (options, callback) ->
+
+	options ?= {}
+	options.name = 'Nest'
+	options.root ?= __dirname
+	options.port ?= process.env.PORT or 9250
+	options.host = process.env.HOST or '127.0.0.1'

-port = process.env.PORT || 9250
-americano.start name: 'template', port: port+	americano.start options, (err, app, server) ->
+		return callback err if err
+
+		module.exports.app = app
+		cb?(null, app, server)
+
+
+	if not module.parent
+		application()


change app name in americano config

change app name in americano config

--- server.coffee
+++ server.coffee
@@ -2,7 +2,7 @@

 start = (port, callback) ->
     require('americano').start
-            name: 'Agenda'
+            name: 'Calendar'
             port: port
             host: process.env.HOST or "0.0.0.0"
     , (app, server) ->


Calculator fixes: [#3739033], [#3739040]

[#3739033] -- pi should multiply when against a number.
[#3739040] -- parentheticals should multiply when against a number.

--- javascripts/lib/math.coffee
+++ javascripts/lib/math.coffee
@@ -20,8 +20,8 @@
       @expression.push part

     # returns part of an expression
-    last: ->
-      _.last(@expression)
+    last: (from_end=0)->
+      @expression[@expression.length - 1 - from_end]

     first: ->
       _.first(@expression)
@@ -143,16 +143,16 @@
       (new ExpressionEvaluation(@subexpression)).results()

   class ExpressionComponent
-    handled: ->
-      @is_handled = true
-    isHandled: ->
-      @is_handled
+    isOperator: -> false
+    isNumber: -> false
     eval: -> @

   class Number extends ExpressionComponent
     initialize: (@opts)->
       @val = @opts.value
-
+
+    isNumber: -> true
+
     negated: ->
       value = @val * -1
       Number.build(value: value)
@@ -204,23 +204,9 @@

   class_mixer(Number)

-  class Square extends ExpressionComponent
-    toDisplay: -> "2"
-    eval: (evaluation, pass)->
-      return if pass != "exponentiation"
-      prev = evaluation.previousValue()
-      if prev
-        evaluation.handledPrevious()
-        squared = parseFloat(prev) * parseFloat(prev)
-        Number.build(value: squared)
-      else
-        throw "Invalid Expression"
-
-  class_mixer(Square)
-
-
   class Exponentiation extends ExpressionComponent
-    toDisplay: -> "^"
+    isOperator: -> true
+    toDisplay: -> 'ˆ'
     eval: (evaluation, pass)->
       return if pass != "exponentiation"

@@ -236,13 +222,14 @@

   class Pi extends ExpressionComponent
     toDisplay: -> "π"
-    eval: ->
+    eval: ()->
       Number.build(value: Math.PI)

   class_mixer(Pi)

   class Addition extends ExpressionComponent
     toDisplay: -> "+"
+    isOperator: -> true
     eval: (evaluation, pass)->
       return if pass != "addition"

@@ -258,6 +245,7 @@

   class Subtraction extends ExpressionComponent
     toDisplay: -> "-"
+    isOperator: -> true
     eval: (evaluation, pass)->
       return if pass != "addition"

@@ -273,6 +261,7 @@
   class_mixer(Subtraction)

   class Multiplication extends ExpressionComponent
+    isOperator: -> true
     eval: (evaluation, pass)->
       return if pass != "multiplication"
       prev = evaluation.previousValue()
@@ -287,6 +276,7 @@
   class_mixer(Multiplication)

   class Division extends ExpressionComponent
+    isOperator: -> true
     toDisplay: -> "÷"
     eval: (evaluation, pass)->
       return if pass != "multiplication"
@@ -317,6 +307,9 @@

   class_mixer(RightParenthesis)

+  ##############
+  # commands!
+  ###############

   class CalculateCommand
     invoke: (expression)->
@@ -353,7 +346,9 @@
   class NumberCommand
     initialize: (@opts)->
       @val = @opts.value
-    invoke: (expression)->
+
+    invoke: (expression)->
+      expression = _ImplicitMultiplication.build().onNeitherOperatorNorNumber(expression)
       last = expression.last()
       if last instanceof Number
         new_last = last.concatenate(@val)
@@ -398,7 +393,10 @@

   class LeftParenthesisCommand
     invoke: (expression)->
-      expression.cloneAndAppend(LeftParenthesis.build())
+      _ImplicitMultiplication.build().
+        onNumeric(expression).
+        cloneAndAppend(LeftParenthesis.build())
+
   class_mixer(LeftParenthesisCommand)

   class RightParenthesisCommand
@@ -411,14 +409,34 @@
       expression.cloneAndAppend(Division.build())
   class_mixer(DivisionCommand)

+
+  class _ImplicitMultiplication
+    onNumeric: (expression)->
+      last = expression.last()
+      if last && last.isNumber()
+        expression.cloneAndAppend(Multiplication.build())
+      else
+        expression
+
+    onNeitherOperatorNorNumber: (expression)->
+      last = expression.last()
+      if last and !last.isNumber()  and !last.isOperator() and !(last instanceof LeftParenthesis)
+        expression.cloneAndAppend(Multiplication.build())
+      else
+        expression
+
+  class_mixer(_ImplicitMultiplication)
+
   class PiCommand
     invoke: (expression)->
-      expression.cloneAndAppend(Pi.build())
+      _ImplicitMultiplication.build().
+        onNumeric(expression).
+        cloneAndAppend(Pi.build())
+
   class_mixer(PiCommand)

   class SquareRootCommand
     invoke: (expression)->
-
       value = expression.evaluate().value() || 0
       root = Math.sqrt(parseFloat(value))
       unless isNaN(root)


Fixed multiline banner

Fixed multiline banner

--- js-library/Gruntfile.coffee
+++ js-library/Gruntfile.coffee
@@ -1,10 +1,12 @@
 # global config:true, file:true, task:true, module: true

-banner_template = '/*!
+banner_template = """
+/*!
 * <%= pkg.title || pkg.name %> - v<%= pkg.version %> - <%= pkg.homepage ? " * " + pkg.homepage : "" %>
 * Date: <%= grunt.template.today("m/d/yyyy") %>
 * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>
-*/'
+*/
+"""

 module.exports = (grunt) ->
   grunt.initConfig


Making the linter happy

Making the linter happy

--- framer/SVG.coffee
+++ framer/SVG.coffee
@@ -3,6 +3,7 @@
 class SVG
 	@isSVG: (svg) ->
 		svg instanceof SVGElement
+

 class SVGPath extends BaseClass


add preview when add data

add preview when add data

--- views/addDataTab.cjsx
+++ views/addDataTab.cjsx
@@ -1,9 +1,11 @@
 {React, ReactBootstrap} = window
-{Button, Input, Label} = ReactBootstrap
+{Button, Input, Label, Modal} = ReactBootstrap
+{relative, join} = require 'path-extra'
 i18n = require '../node_modules/i18n'
 {__} = i18n

 TagsInputContainer = require './tagsInputContainer'
+HenseiItem = require './henseiItem'

 AddDataTab = React.createClass
   getInitialState: ->
@@ -11,6 +13,8 @@
     saveDisable: true
     tags: []
     deckChecked: [false, false, false, false]
+    showPre: false
+    deck: ''
   componentWillReceiveProps: (nextProps)->
     if nextProps.indexKey is nextProps.selectedKey
       @setState
@@ -44,6 +48,15 @@
       deckChecked: [false, false, false, false]
       title: ''
       tags: []
+  handlePreClick: ->
+    {deckChecked, title, tags} = @state
+    deck = @props.getDeckDetail deckChecked, tags
+    @setState
+      deck: deck
+      showPre: true
+  close: ->
+    @setState
+      showPre: false
   handleClickCheckbox: (index) ->
     {deckChecked} = @state
     if deckChecked isnt []
@@ -84,11 +97,28 @@
       <div>
         <Button bsSize='small'
                 disabled={@state.saveDisable}
+                onClick={@handlePreClick}
+                block>
+          {__ 'Save'}
+        </Button>
+        <Button bsSize='small'
+                disabled={@state.saveDisable}
                 onClick={@handleSaveClick}
                 block>
           {__ 'Save'}
         </Button>
       </div>
+      <Modal show={@state.showPre} container={this}>
+        <Modal.Header>
+          <Modal.Title id='contained-modal-title'>Preview</Modal.Title>
+        </Modal.Header>
+        <Modal.Body>
+          <HenseiItem deckItem={@state.deck}/>
+        </Modal.Body>
+        <Modal.Footer>
+          <Button onClick={@close}>Close</Button>
+        </Modal.Footer>
+      </Modal>
     </div>

 module.exports = AddDataTab


extract gorilla dimensions to variables and calculate building's middle position to correctly place gorilla

--- views/gorillas.coffee
+++ views/gorillas.coffee
@@ -1,6 +1,6 @@
 class Building
-  constructor:(@context) ->
-    @width = 90 + Math.floor(Math.random()*40)
+  constructor:(@context, @canvas_height) ->
+    @width = 70 + Math.floor(Math.random()*40)
     @base_height = 100
     @randomize_color()

@@ -8,10 +8,13 @@
     return @x

   position_at_y:() ->
-    return  640-@height
+    return  @canvas_height-@height

   end_position:() ->
     return @position_at_x()+@width
+
+  middle_position: ->
+    return @position_at_x() + (@end_position() - @position_at_x())/2

   draw:(@x, @y) ->
     @context.fillStyle = @color
@@ -104,6 +107,8 @@
   constructor: ->
     @empty = true
     @canvas = document.getElementById "gorillas"
+    @width = @canvas.width
+    @height = @canvas.height
     @context = @canvas.getContext "2d"
     @color = '#00FFFF'
     @padding = 1
@@ -127,12 +132,12 @@

   draw_buildings:()->
     position = 0
-    for [1...10]
+    while position < @width
       building = @draw_building(position)
       position = building.end_position() + @padding

   draw_building:(x) ->
-    building = new Building(@context)
+    building = new Building(@context, @height)

     y = Math.floor(Math.random()*300)
     building.draw(x, y)
@@ -154,11 +159,11 @@
   draw_gorillas: ->
     building = @buildings[Math.floor(Math.random()*3)]
     @player_1 = new Gorilla(@context)
-    @player_1.draw(building.position_at_x(), building.position_at_y())
+    @player_1.draw(building.middle_position(), building.position_at_y())

     building = @buildings[Math.floor(Math.random()*6)+3]
     @player_2 = new Gorilla(@context)
-    @player_2.draw(building.position_at_x(), building.position_at_y())
+    @player_2.draw(building.middle_position(), building.position_at_y())

   redraw_gorillas: ->
     @player_1.redraw()
@@ -185,6 +190,8 @@

 class Gorilla
   constructor:(@context) ->
+    @width = 25
+    @height = 25

   image: ->
     image = new Image()
@@ -192,15 +199,15 @@
     image

   draw:(x, y) ->
-    @x ||= x+30
-    @y ||= y-40
-    @context.drawImage(@image(), @x, @y, 40, 40)
+    @x ||= x-@width/2
+    @y ||= y-@height
+    @context.drawImage(@image(), @x, @y, @width, @height)

   redraw:() ->
     @draw(@x, @y)

   grab_banana:(force, angle) ->
-    @banana = new Banana(@context, @x+30, @y-30, force, angle)
+    @banana = new Banana(@context, @x+@width, @y-@height, force, angle)

   throw_banana: ->
     @banana.draw_frame()