console keymap fix

console keymap fix

--- keymaps/julia-client.cson
+++ keymaps/julia-client.cson
@@ -18,14 +18,14 @@
   'cmd-j cmd-g': 'julia-client:goto-symbol'

 '.platform-darwin .item-views > atom-text-editor[data-grammar="source julia"],
- .platform-darwin ink-console.julia,
- .platform-darwin ink-console.julia atom-text-editor':
+ .platform-darwin .julia-console.julia,
+ .platform-darwin .julia-console.julia atom-text-editor':
   'ctrl-c': 'julia-client:interrupt-julia'
   'cmd-j cmd-m': 'julia-client:set-working-module'

 '.platform-darwin .item-views > atom-text-editor[data-grammar="source julia"].debug,
- .platform-darwin ink-console.julia.debug,
- .platform-darwin ink-console.julia.debug atom-text-editor':
+ .platform-darwin .julia-console.julia.debug,
+ .platform-darwin .julia-console.julia.debug atom-text-editor':
   'ctrl-l': 'julia-debug:step-to-next-line'
   'ctrl-m': 'julia-debug:step-to-next-expression'
   'ctrl-f': 'julia-debug:finish-function'
@@ -54,10 +54,10 @@
   'alt-x': 'julia-debug:toggle-breakpoint'
   'ctrl-j ctrl-g': 'julia-client:goto-symbol'

-'.platform-win32 ink-console.julia,
- .platform-win32 ink-console.julia atom-text-editor,
- .platform-linux ink-console.julia,
- .platform-linux ink-console.julia atom-text-editor':
+'.platform-win32 .julia-console.julia,
+ .platform-win32 .julia-console.julia atom-text-editor,
+ .platform-linux .julia-console.julia,
+ .platform-linux .julia-console.julia atom-text-editor':
   'ctrl-shift-c': 'julia-client:interrupt-julia'
   'ctrl-j ctrl-m': 'julia-client:set-working-module'


Specs for label rendering

Specs for label rendering

--- spec/autocomplete-manager-integration-spec.coffee
+++ spec/autocomplete-manager-integration-spec.coffee
@@ -788,6 +788,89 @@
           atom.commands.dispatch(suggestionListView, 'autocomplete-plus:select-next')
           expect(items[0]).toHaveClass('selected')

+    describe "label rendering", ->
+      describe "when no labels are specified", ->
+        beforeEach ->
+          spyOn(provider, 'getSuggestions').andCallFake (options) ->
+            [text: 'ok']
+
+        it "displays the text in the suggestion", ->
+          triggerAutocompletion(editor)
+          runs ->
+            iconContainer = editorView.querySelector('.autocomplete-plus li .icon-container')
+            leftLabel = editorView.querySelector('.autocomplete-plus li .right-label')
+            rightLabel = editorView.querySelector('.autocomplete-plus li .right-label')
+
+            expect(iconContainer.childNodes).toHaveLength 0
+            expect(leftLabel.childNodes).toHaveLength 0
+            expect(rightLabel.childNodes).toHaveLength 0
+
+      describe "when `type` is specified", ->
+        beforeEach ->
+          spyOn(provider, 'getSuggestions').andCallFake (options) ->
+            [text: 'ok', type: 'omg']
+
+        it "displays an icon in the icon-container", ->
+          triggerAutocompletion(editor)
+          runs ->
+            icon = editorView.querySelector('.autocomplete-plus li .icon-container .icon')
+            expect(icon.innerHTML).toBe('o')
+
+      describe "when `iconHTML` is specified", ->
+        beforeEach ->
+          spyOn(provider, 'getSuggestions').andCallFake (options) ->
+            [text: 'ok', iconHTML: '']
+
+        it "displays an icon in the icon-container", ->
+          triggerAutocompletion(editor)
+          runs ->
+            icon = editorView.querySelector('.autocomplete-plus li .icon-container .icon .omg')
+            expect(icon).toExist()
+
+      describe "when `rightLabel` is specified", ->
+        beforeEach ->
+          spyOn(provider, 'getSuggestions').andCallFake (options) ->
+            [text: 'ok', rightLabel: 'sometext']
+
+        it "displays the text in the suggestion", ->
+          triggerAutocompletion(editor)
+          runs ->
+            label = editorView.querySelector('.autocomplete-plus li .right-label')
+            expect(label).toHaveText('sometext')
+
+      describe "when `rightLabelHTML` is specified", ->
+        beforeEach ->
+          spyOn(provider, 'getSuggestions').andCallFake (options) ->
+            [text: 'ok', rightLabelHTML: 'sometext']
+
+        it "displays the text in the suggestion", ->
+          triggerAutocompletion(editor)
+          runs ->
+            label = editorView.querySelector('.autocomplete-plus li .right-label .something')
+            expect(label).toHaveText('sometext')
+
+      describe "when `leftLabel` is specified", ->
+        beforeEach ->
+          spyOn(provider, 'getSuggestions').andCallFake (options) ->
+            [text: 'ok', leftLabel: 'sometext']
+
+        it "displays the text in the suggestion", ->
+          triggerAutocompletion(editor)
+          runs ->
+            label = editorView.querySelector('.autocomplete-plus li .left-label')
+            expect(label).toHaveText('sometext')
+
+      describe "when `leftLabelHTML` is specified", ->
+        beforeEach ->
+          spyOn(provider, 'getSuggestions').andCallFake (options) ->
+            [text: 'ok', leftLabelHTML: 'sometext']
+
+        it "displays the text in the suggestion", ->
+          triggerAutocompletion(editor)
+          runs ->
+            label = editorView.querySelector('.autocomplete-plus li .left-label .something')
+            expect(label).toHaveText('sometext')
+
   describe 'when opening a file without a path', ->
     beforeEach ->
       waitsForPromise ->


homeview loggedin view added

homeview loggedin view added

--- client/landing/app/Applications/Home.kdapplication/Views/homeintroview.coffee
+++ client/landing/app/Applications/Home.kdapplication/Views/homeintroview.coffee
@@ -33,6 +33,15 @@
           partial     : 'or'
           itemClass   : KDCustomHTMLView
           tagName     : 'span'
+        # firstname     :
+        #   name        : 'firstname'
+        #   placeholder : 'First name'
+        #   cssClass    : 'half'
+        #   nextElement     :
+        #     lastname      :
+        #       name        : 'lastname'
+        #       placeholder : 'Last name'
+        #       cssClass    : 'half'
         username      :
           name        : 'username'
           placeholder : 'Desired username'
@@ -72,17 +81,30 @@
     @setClass 'out'

   pistachio:->
-    """
-

-    {{> @slogan}}
-    {{> @subSlogan}}
-

-
{{> @videoThumb}}

-

-

-
-    {{> @form}}
-    {{> @toc}}
-
-    """
+    if KD.isLoggedIn()
+      """
+

+      {{> @slogan}}
+      {{> @subSlogan}}
+

+
+

+
{{> @videoThumb}}

+

+
+      """
+    else
+      """
+

+      {{> @slogan}}
+      {{> @subSlogan}}
+

+
{{> @videoThumb}}

+

+

+
+      {{> @form}}
+      {{> @toc}}
+
+      """


switchTag should return value

switchTag should return value

--- src/dom.coffee
+++ src/dom.coffee
@@ -273,7 +273,7 @@

   switchTag: (node, newTag) ->
     newTag = newTag.toUpperCase()
-    return if node.tagName == newTag
+    return node if node.tagName == newTag
     newNode = node.ownerDocument.createElement(newTag)
     attributes = DOM.getAttributes(node)
     this.moveChildren(newNode, node) unless DOM.VOID_TAGS[newTag]?


ie needs explicit arguments

ie needs explicit arguments

--- src/dom.coffee
+++ src/dom.coffee
@@ -164,7 +164,7 @@
       else return ""

   getTextNodes: (root) ->
-    walker = root.ownerDocument.createTreeWalker(root, NodeFilter.SHOW_TEXT)
+    walker = root.ownerDocument.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false)
     nodes = []
     while node = walker.nextNode()
       nodes.push(node)


updated to v0.0.72

updated to v0.0.72

--- test/fn.coffee
+++ test/fn.coffee
@@ -16,7 +16,7 @@

     source = './source'
     target = [
-      "#{$$.path.base}/source"
+      $$.fn.normalizePath "#{$$.path.base}/source"
     ]

     res = $$.fn.formatPath source
@@ -32,9 +32,9 @@
       '!**/include/**'
     ]
     target = [
-      "#{$$.path.base}/source"
-      "#{$$.path.home}/opt"
-      "!#{$$.path.base}/**/include/**"
+      $$.fn.normalizePath "#{$$.path.base}/source"
+      $$.fn.normalizePath "#{$$.path.home}/opt"
+      $$.fn.normalizePath "!#{$$.path.base}/**/include/**"
     ]

     res = $$.fn.formatPath source
@@ -46,7 +46,7 @@

     source = '/opt/a/b/../c'
     target = [
-      '/opt/a/c'
+      $$.fn.normalizePath '/opt/a/c'
     ]

     res = $$.fn.formatPath source
@@ -59,7 +59,7 @@
   it "$$.fn.normalizePath('./source')", ->

     source = './source'
-    target = "#{$$.path.base}/source"
+    target = $$.fn.normalizePath "#{$$.path.base}/source"

     res = $$.fn.normalizePath source

@@ -69,7 +69,7 @@
   it "$$.fn.normalizePath('~/opt')", ->

     source = '~/opt'
-    target = "#{$$.path.home}/opt"
+    target = $$.fn.normalizePath "#{$$.path.home}/opt"

     res = $$.fn.normalizePath source

@@ -79,7 +79,7 @@
   it "$$.fn.normalizePath('./a/b/../c')", ->

     source = './a/b/../c'
-    target = "#{$$.path.base}/a/c"
+    target = $$.fn.normalizePath "#{$$.path.base}/a/c"

     res = $$.fn.normalizePath source


planned out the new commands, these just need to be tested

--- src/organizations.coffee
+++ src/organizations.coffee
@@ -60,29 +60,60 @@
   robot.respond /gho$/i, (msg) ->
     org.summary msg

-  robot.respond /gho list (teams|repos|members)\s?(?:["'](.*?)['"])?/i, (msg) ->
+  robot.respond /gho list (teams|members)|(?:(repos)(?:[\/])?(public|private)?)/i, (msg) ->
     org.list[msg.match[1]] msg, msg.match[2]

-  robot.respond /gho create (team|repo) ["'](.*?)['"](?:[:])?(?:["'](.*?)['"])?(?:[:])?(?:["'](.*?)['"])?/i, (msg) ->
+
+  robot.respond /gho create (team|repo) (\w.+)/i, (msg) ->
     unless isAdmin msg.message.user
-      msg.reply "Sorry, only admins can use 'create' commands"
+      msg.reply "Only admins can use `create` commands"
     else
-      org.create[msg.match[1]] msg, msg.match[2], msg.match[3], msg.match[4]
+      msg.send "done"

-  robot.respond /gho add (repos|members) ["'](.*?)['"](?: to team) ["'](.*?)['"]/i, (msg) ->
+
+  # robot.respond /gho create (team|repo) ["'](.*?)['"](?:[:])?(?:["'](.*?)['"])?(?:[:])?(?:["'](.*?)['"])?/i, (msg) ->
+  #   unless isAdmin msg.message.user
+  #     msg.reply "Sorry, only admins can use 'create' commands"
+  #   else
+  #     org.create[msg.match[1]] msg, msg.match[2], msg.match[3], msg.match[4]
+
+
+
+  robot.respond /gho add (members|repos) (\w.+) to team (\w.+)/i, (msg) ->
     unless isAdmin msg.message.user
-      msg.reply "Sorry, only admins can use 'add' commands"
+      msg.reply "Only admins can use `add` commands"
     else
-      org.add[msg.match[1]] msg, msg.match[2], msg.match[3]
+      msg.send "done"

-  robot.respond /gho remove (repos|members) ["'](.*?)['"](?: from) (team|everything)\s?(?:["'](.*?)['"])?/i, (msg) ->
+  # robot.respond /gho add (repos|members) ["'](.*?)['"](?: to team) ["'](.*?)['"]/i, (msg) ->
+  #   unless isAdmin msg.message.user
+  #     msg.reply "Sorry, only admins can use 'add' commands"
+  #   else
+  #     org.add[msg.match[1]] msg, msg.match[2], msg.match[3]
+
+
+
+  robot.respond /gho remove (members|repos) (\w.+) from team (\w.+)/i, (msg) ->
     unless isAdmin msg.message.user
-      msg.reply "Sorry, only admins can use 'remove' commands"
+      msg.reply "Only admins can use `remove` commands"
     else
-      org.remove[msg.match[1]] msg, msg.match[2], msg.match[4]
+      msg.send "done"

-  robot.respond /gho delete (team) ["'](.*?)['"]/i, (msg) ->
+  # robot.respond /gho remove (repos|members) ["'](.*?)['"](?: from) (team|everything)\s?(?:["'](.*?)['"])?/i, (msg) ->
+  #   unless isAdmin msg.message.user
+  #     msg.reply "Sorry, only admins can use 'remove' commands"
+  #   else
+  #     org.remove[msg.match[1]] msg, msg.match[2], msg.match[4]
+
+
+  robot.respond /gho delete team (\w.+)/, (msg) ->
     unless isAdmin msg.message.user
-      msg.reply "Sorry, only admins can use 'delete' commands"
+      msg.reply "Only admins can use the `delete` commands"
     else
-      org.delete[msg.match[1]] msg, msg.match[2]
+      msg.send "done"
+
+#   robot.respond /gho delete (team) ["'](.*?)['"]/i, (msg) ->
+#     unless isAdmin msg.message.user
+#       msg.reply "Sorry, only admins can use 'delete' commands"
+#     else
+#       org.delete[msg.match[1]] msg, msg.match[2]


fix createComputeProvider function

fix createComputeProvider function

--- workers/social/testhelper/models/computeproviders/computeproviderhelper.coffee
+++ workers/social/testhelper/models/computeproviders/computeproviderhelper.coffee
@@ -1,7 +1,9 @@
 { _
   daisy
   expect
-  withConvertedUser }    = require '../../index'
+  ObjectId
+  withConvertedUser
+  generateRandomString } = require '../../index'
 { createProvisioner }    = require './provisionerhelper'
 { createStackTemplate }  = require './stacktemplatehelper'
 { createCredential }     = require './credentialhelper'
@@ -75,20 +77,33 @@
     _createComputeProvider = (client, options, callback) ->
       computeProvider = null
       client.r        = { group : groupSlug, user, account }
+      stack           = options.stack ? null
       label           = generateRandomString()
+      provider        = 'aws'
       generatedFrom   = new ObjectId
-      provider        = 'aws'

-      expect(stack).to.exist
-      computeProviderOptions = {
-        client, stack, provider, label, generatedFrom
-        users : []
-        provisioners : [data.provisioner ? 'test_provisioner']
-      }
+      _queue = [

-      ComputeProvider.create client, computeProviderOptions, (err, data_) ->
-        expect(err).to.not.exist
-        callback data_
+        ->
+          return queue.next()  if stack
+          _createStack client, options, (data_) ->
+            { stack } = data_
+            _queue.next()
+
+        ->
+          computeProviderOptions = {
+            client, stack, provider, label, generatedFrom
+            users : []
+            provisioners : [data.provisioner ? 'test_provisioner']
+          }
+
+          ComputeProvider.create client, computeProviderOptions, (err, machine) ->
+            expect(err).to.not.exist
+            callback { machine, stack }
+
+      ]
+
+      daisy _queue

     models.forEach (model) ->


Make sure the replaced files have the same mode as original

--- src/path-replacer.coffee
+++ src/path-replacer.coffee
@@ -53,7 +53,11 @@
         result = {filePath, replacements}
         @emit('path-replaced', result)

+      tempStat = fs.statSync(output.path)
+      origStat = fs.statSync(filePath)
+      fs.chmodSync(output.path, origStat.mode) if origStat.mode != tempStat.mode
       fs.renameSync(output.path, filePath)
+
       doneCallback(result)

     reader.pipe(replacer).pipe(output)


Simplify the date reformatting

Simplify the date reformatting

Signed-off-by: Marko Locher <9efe9a5a1da5f41a4eb7599f2715dc24abf5bbc8@locher.at>

--- src/plugins/eowcs.coffee
+++ src/plugins/eowcs.coffee
@@ -2,9 +2,8 @@
     constructor: (@options = {})->

         @formatDate = (date) ->
-            date = date.toISOString()
-            date = date.substring(0, date.length - 5)
-            date + "Z"
+            # All EO-WCS servers used for testing can't handle subsecond precision dates, so we strip this information
+            date.toISOString().substring(0, 19) + "Z"

         callback = (start, end, callback) =>
             request = d3.xhr(WCS.EO.KVP.describeEOCoverageSetURL(@options.url, @options.eoid, { subsetTime: [ @formatDate(start), @formatDate(end) ] }))


:lipstick:

:lipstick:

--- src/git.coffee
+++ src/git.coffee
@@ -102,10 +102,10 @@

   # Returns the corresponding {Repository}
   getRepo: (path) ->
-    unless @repo?
+    if @repo?
+      @repo.submoduleForPath(path) ? @repo
+    else
       throw new Error("Repository has been destroyed")
-
-    @repo.submoduleForPath(path) ? @repo

   # Reread the index to update any values that have changed since the
   # last time the index was read.


style: prefer dot notation

style: prefer dot notation

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -691,8 +691,8 @@
             You've already invited #{email} to join this group.
             """
         else
-          params['invitationType'] = 'invitation'
-          params['status']         = 'sent'
+          params.invitationType = 'invitation'
+          params.status         = 'sent'

           invitationRequest = new JInvitationRequest params
           invitationRequest.save (err)=>


Improve robot hear regex of foaas

Improve robot hear regex of foaas

--- src/scripts/foass.coffee
+++ src/scripts/foass.coffee
@@ -13,7 +13,7 @@

 module.exports = (robot) ->
-  robot.hear /^fu ?(\w+)?/i, (msg) ->
+  robot.hear /^fu(?:\s)(?=([\w ]+))/i, (msg) ->
     options = [
       'off',
       'you',


Add spacing between routes

Add spacing between routes

--- src/scripts/httpd.coffee
+++ src/scripts/httpd.coffee
@@ -2,12 +2,16 @@
 spawn = require('child_process').spawn

 module.exports = (robot) ->
+
   robot.router.get "/hubot/version", (req, res) ->
     res.end robot.version
+
   robot.router.post "/hubot/ping", (req, res) ->
     res.end "PONG"
+
   robot.router.get "/hubot/time", (req, res) ->
     res.end "Server time is: #{new Date()}"
+
   robot.router.get "/hubot/info", (req, res) ->
     child = spawn('/bin/sh', ['-c', "echo I\\'m $LOGNAME@$(hostname):$(pwd) \\($(git rev-parse HEAD)\\)"])


Fix janky syntax error

Fix janky syntax error

--- src/scripts/janky.coffee
+++ src/scripts/janky.coffee
@@ -201,5 +201,5 @@
     app = msg.match[1]
     del "#{app}", {}, (err, statusCode, body) ->
       if statusCode != 200
-        msg.reply "I got an error removing #{app}; sometimes deleting all the old commits/branches times out the unicorn. Maybe
+        msg.reply "I got an error removing #{app}; sometimes deleting all the old commits/branches times out the unicorn. Maybe try again?"
       msg.send body


[Cartilage.Views.ImageView] Additional fixes for Internet Explorer 8.

--- app/assets/javascripts/cartilage/views/image_view.js.coffee
+++ app/assets/javascripts/cartilage/views/image_view.js.coffee
@@ -51,11 +51,9 @@

       @isRendered = true

-    # unless @_loadEventsBound
-      # @_loadEventsBound = true
-
     # Update the Image Source
-    @imageElement.attr("src", @imageAddress) if @imageAddress?
+    unless @imageAddress == @imageElement.attr("src")
+      @imageElement.attr("src", @imageAddress)

     @


Fixed a nasty little crash in ImageView that occurred when the error event is triggered.

--- app/assets/javascripts/cartilage/views/image_view.js.coffee
+++ app/assets/javascripts/cartilage/views/image_view.js.coffee
@@ -26,6 +26,11 @@
   # Denotes whether or not the image has finished loading.
   #
   isLoaded: false
+
+  #
+  # Denotes whether or not an error occurred while loading.
+  #
+  isError: false

   #
   # Initializes a new instance of the split view. Pass firstView and
@@ -68,14 +73,17 @@
   clear: (options = {}) ->
     @isLoaded = false
     @imageAddress = null
+    ($ @imageElement).off()
     @imageElement.hide().attr("src", null)
     @trigger "cleared" unless options["silent"]

   handleLoadEvent: (event) =>
     @isLoaded = true
+    @isError = false
     @imageElement.fadeIn() unless @imageElement.is ":visible"
     @trigger "load", event

   handleErrorEvent: (event) =>
     @clear()
+    @isError = true
     @trigger "error", event


be specific about which cases to stop listening to `change` event

We were turning off listening to the change event so that we didn't set up a new event listener every time we called this method without turning them off, but this was having a side-effect of turning off the change event for EVERYTHING. This gets specific about when to stop listening.

--- app/assets/javascripts/views/patient_results_view.js.coffee
+++ app/assets/javascripts/views/patient_results_view.js.coffee
@@ -32,10 +32,14 @@
       $(document).on 'scroll', @scrollHandler
     destroyed: ->
       $(document).off 'scroll', @scrollHandler
+      @query.off 'change', @setCollectionAndFetch
     collection:
       sync: -> @isFetching = false

   initialize: ->
+    @setCollectionAndFetch = =>
+      @setCollection new Thorax.Collections.PatientResults([], parent: @query, population: @population, providerId: @providerId), render: true
+      @collection.fetch()
     @isFetching = false
     @scrollHandler = =>
       distanceToBottom = $(document).height() - $(window).scrollTop() - $(window).height()
@@ -46,12 +50,8 @@
     @setQuery @query

   setQuery: (query) ->
-    @query.off 'change'
+    @query.off 'change', @setCollectionAndFetch
     @query = query
     @isEpisodeOfCare = @query.parent.get('episode_of_care')
-    setCollection = =>
-      @setCollection new Thorax.Collections.PatientResults([], parent: @query, population: @population, providerId: @providerId), render: true
-      @collection.fetch()
-    @query.on 'change', setCollection
-    if @query.isNew() then @query.save() else setCollection()
-
+    @query.on 'change', @setCollectionAndFetch
+    if @query.isNew() then @query.save() else @setCollectionAndFetch()


CLI reworking the development server sources resolver so it catched 'index.html' in subfolders

--- cli/commands/server.coffee
+++ cli/commands/server.coffee
@@ -50,35 +50,40 @@
   # listening all the static content in the user project
   server.get /^\/(.*?)$/, (req, res) ->
     minify    = req.query.minify is 'true'
-    filename  = req.params[0] || 'index'
-    extension = filename.split('.')
-    extension = extension[extension.length - 1]
     shared    = "#{__dirname}/../server"

-    unless extension in ['css', 'html', 'js']
-      filename += '.html'
-      extension = 'html'
+    file_in = (directory)->
+      for ext in ['', '.html', 'index.html', '/index.html']
+        relname  = "#{req.params[0]}#{ext}"
+        fullname = "#{directory}/#{relname}"
+        if path.existsSync(fullname) and !fs.statSync(fullname).isDirectory()
+          return relname

-    switch extension
-      when 'css' then content_type = 'text/css'
-      when 'js'  then content_type = 'text/javascript'
-      else            content_type = 'text/html'
+      return false

-    if path.existsSync("#{process.cwd()}/#{filename}")
-      console.log("") if extension is "html"
-      console.log(" Sending:   "+ "/#{filename} (#{content_type})".grey)
+    content_type = (name)->
+      extension = (name || '').split('.')
+      switch extension[extension.length - 1]
+        when 'css' then return 'text/css'
+        when 'js'  then return 'text/javascript'
+        else            return 'text/html'
+
+
+    if filename = file_in(process.cwd())
+      console.log("") if filename.substr(filename.length-4) is 'html'
+      console.log(" Sending:   "+ "/#{filename} (#{content_type(filename)})".grey)
       data = fs.readFileSync("#{process.cwd()}/#{filename}")

-    else if path.existsSync("#{shared}/#{filename}")
-      console.log(" Sending:   "+ "/#{filename} (text/css) from shared directory".grey)
+    else if filename = file_in(shared)
+      console.log(" Sending:   "+ "/#{filename} -> ~/.lovely/server/#{filename}".grey)
       data = fs.readFileSync("#{shared}/#{filename}")

     else
-      console.log("\n Sending:   "+ "404 Error".red + " /#{filename} is not found".grey)
+      console.log("\n Sending:   "+ "404 Error".red + " /#{req.params[0]} is not found".grey)
       data = fs.readFileSync("#{shared}/404.html")

     res.charset = 'utf-8'
-    res.contentType(content_type)
+    res.contentType(content_type(filename))
     res.send data


default plugin udpated

default plugin udpated

--- plugins/help.coffee
+++ plugins/help.coffee
@@ -60,9 +60,8 @@
   if content.match /^time$/i
     send "冥王星引力精准校时：" + new Date()

-  ret = content.match /^echo (.*)/i
-  if ret
-    send "哈哈，" + ret[1]
+  if ret = content.match /^echo (.*)/i
+    send ret[1]

   if content.match /^uptime$/i
     secs  = process.uptime()


styling of link dialog

styling of link dialog

--- plugins/link.coffee
+++ plugins/link.coffee
@@ -13,17 +13,18 @@
             dialogOpts:
                 autoOpen: false
                 width: 540
-                height: 120
+                height: 95
                 title: "Enter Link"
                 modal: true
                 resizable: false
-                draggable: true
+                draggable: false
+                dialogClass: 'hallolink-dialog'

         _create: ->
             widget = this

             dialogId = "#{@options.uuid}-dialog"
-            dialog = jQuery "
<form action=\"#\" method=\"post\" class=\"linkForm\"><input class=\"url\" type=\"text\" name=\"url\" size=\"40\" value=\"#{@options.defaultUrl}\" /><input type=\"submit\" id=\"addlinkButton\" value=\"Insert\" /></form>
"
+            dialog = jQuery "
<form action=\"#\" method=\"post\" class=\"linkForm\"><input class=\"url\" type=\"text\" name=\"url\" value=\"#{@options.defaultUrl}\" /><input type=\"submit\" id=\"addlinkButton\" value=\"Insert\" /></form>
"
             urlInput = jQuery('input[name=url]', dialog).focus (e)->
                 this.select()
             dialogSubmitCb = () ->


Generic list rendering for xrefs.

Generic list rendering for xrefs.

--- client/widgets_form.coffee
+++ client/widgets_form.coffee
@@ -78,13 +78,11 @@
   get_xref field, $ x for x in xml.children()

 xreflist = (field, value, xml, options) ->
-  if not xml.children().length
-    return widget "xreflist_empty", field, null, xml, options
   opt = $.extend {}, options
   opt.records = field.records
   opt.schema = field.schema
-  [(render_list opt, get_xrefs field, xml),
-   (list_selecting_button options.containment)]
+  opt.desc = field.desc
+  (render_list opt, get_xrefs field, xml)

 @WIDGETS =
   textline:       text_input


Social: add deleted code back

Social: add deleted code back

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -1391,16 +1391,11 @@

   createSocialApiChannels: (client, callback) ->

-    if @socialApiChannelId and @socialApiAnnouncementChannelId
-      return callback null, {
-        @socialApiChannelId,
-        @socialApiAnnouncementChannelId
-      }
-
     @fetchOwner (err, owner) =>
       return callback err if err?
       unless owner
         return callback { message: "Owner not found for #{@slug} group" }
+
       owner.createSocialApiId (err, socialApiId) =>
         return callback err if err?
         # required data for creating a channel
@@ -1411,17 +1406,29 @@
           privacyConstant : privacy

         @createGroupChannel client, options, (err, groupChannelId) =>
-          return callback err if err?
-
-          @createAnnouncementChannel client, options, (err, announcementChannelId) ->
-            return callback err if err?
-
-            return callback null, {
-              # channel id for #public - used as group channel
-              socialApiChannelId             : groupChannelId,
-              # channel id for #koding - used for announcements
-              socialApiAnnouncementChannelId : announcementChannelId
-            }
+          return callback err  if err?
+
+          # announcement channel will only be created for koding channel
+          if @slug is 'koding'
+
+            @createAnnouncementChannel client, options, (err, announcementChannelId) ->
+              return callback err if err?
+
+              return callback null, {
+                # channel id for #public - used as group channel
+                socialApiChannelId             : groupChannelId,
+                # channel id for #koding - used for announcements
+                socialApiAnnouncementChannelId : announcementChannelId
+              }
+
+          else
+            @createDefaultChannel client, options, (err, defaultChannelId) ->
+              return callback err if err?
+
+              return callback null, {
+                socialApiChannelId: groupChannelId
+                socialApiDefaultChannelId: defaultChannelId
+              }

   createGroupChannel:(client, options, callback) ->
@@ -1432,9 +1439,16 @@
     return @createSocialAPIChannel client, options, callback

   createAnnouncementChannel:(client, options, callback) ->
-    options.name = if @slug is 'koding' then 'changelog' else @slug
+    options.name = 'changelog'
     options.varName = 'socialApiAnnouncementChannelId'
     options.typeConstant = 'announcement'
+
+    return @createSocialAPIChannel client, options, callback
+
+  createDefaultChannel:(client, options, callback) ->
+    options.name = @slug
+    options.varName = 'socialApiDefaultChannelId'
+    options.typeConstant = 'topic'

     return @createSocialAPIChannel client, options, callback

@@ -1455,8 +1469,9 @@
     doRequest 'createChannel', client, defaultChannel, (err, channel) =>
       return callback err if err

-      op = { $set:{} }
+      op = { $set: {}, $push: {} }
       op.$set[varName] = channel.channel.id
+      op.$push['defaultChannels'] = channel.channel.id

       @update op, (err) ->
         return callback err if err


Remove isMounted() call from within render function

--- src/index.cjsx
+++ src/index.cjsx
@@ -34,7 +34,7 @@
     if @state.componentWidth is 0

     # If we're server rendering and the user has disabled server rendering.
-    else if not @isMounted() and @props.disableServerRender
+    else if @props.disableServerRender

     else
       [componentWidth, componentsPerRow] = calculateLayout(@props, @state)


ctx.log might not exists

ctx.log might not exists

--- src/run.coffee
+++ src/run.coffee
@@ -74,7 +74,7 @@
             done = (err, statusOrMsg) =>
               clearTimeout timeout if timeout
               if err and (retry is true or ++attempts < retry)
-                ctx.log "Get error #{err.message}, retry #{attempts} of #{retry}"
+                ctx.log? "Get error #{err.message}, retry #{attempts} of #{retry}"
                 return setTimeout(run, 1)
               if err
               then emit_action ctx.FAILED


Handle existing hubot brains with incomplete user data

--- src/slack.coffee
+++ src/slack.coffee
@@ -32,6 +32,7 @@
     @client.on 'close', @.close
     @client.on 'message', @.message
     @client.on 'userChange', @.userChange
+    @robot.brain.on 'loaded', @.brainLoaded

     # Start logging in
     @client.login()
@@ -53,6 +54,15 @@

     for id, user of @client.users
       @userChange user
+
+  brainLoaded: =>
+    # once the brain has loaded, reload all the users from the client
+    for id, user of @client.users
+      @userChange user
+
+    # also wipe out any broken users stored under usernames instead of ids
+    for id, user of @robot.brain.data.users
+      if id is user.name then delete @robot.brain.data.users[user.id]

   userChange: (user) =>
     newUser = {name: user.name, email_address: user.profile.email}


Add test for repeating indent operations

--- spec/operators-spec.coffee
+++ spec/operators-spec.coffee
@@ -881,6 +881,12 @@
         expect(editor.getText()).toBe "  12345\nabcde\nABCDE"
         expect(editor.getSelectedText()).toBe "  12345\n"

+      it "allows repeating the operation", ->
+        keydown("escape")
+        keydown(".")
+        expect(editorView).toHaveClass 'command-mode'
+        expect(editor.getText()).toBe "    12345\nabcde\nABCDE"
+
   describe "the < keybinding", ->
     beforeEach ->
       editor.setText("  12345\n  abcde\nABCDE")


Tests for Put operator replacing current selection

--- spec/operators-spec.coffee
+++ spec/operators-spec.coffee
@@ -541,6 +541,14 @@

           expect(editor.getText()).toBe "abcdetwo three\none "

+      describe "with a selection", ->
+        beforeEach ->
+          editor.selectRight()
+          keydown('p')
+
+        it "replaces the current selection", ->
+          expect(editor.getText()).toBe "34512\n"
+          expect(editor.getCursorScreenPosition()).toEqual [0, 2]

     describe "with linewise contents", ->
       beforeEach ->
@@ -552,6 +560,18 @@
       it "inserts the contents of the default register", ->
         expect(editor.getText()).toBe "012\n 345"
         expect(editor.getCursorScreenPosition()).toEqual [1, 1]
+
+    describe "with linewise contents", ->
+      beforeEach ->
+        editor.getBuffer().setText("012")
+        editor.setCursorScreenPosition([0, 1])
+        editor.selectRight()
+        vimState.setRegister('"', text: " 345\n", type: 'linewise')
+        keydown('p')
+
+      it "replaces the current selection", ->
+        expect(editor.getText()).toBe "0 345\n2"
+        expect(editor.getCursorScreenPosition()).toEqual [0, 2]

     describe "with linewise contents", ->
       beforeEach ->


Add a basic test for opening an editor in largeFileMode if >= 2MB

--- spec/workspace-spec.coffee
+++ spec/workspace-spec.coffee
@@ -224,6 +224,17 @@
               expect(workspace.paneContainer.root.children[0]).toBe pane1
               expect(workspace.paneContainer.root.children[1]).toBe pane4

+    describe "when the file is large (over 2mb)", ->
+      it "opens the editor with largeFileMode: true", ->
+        spyOn(fs, 'getSizeSync').andReturn 2 * 1048577 # 2MB
+
+        editor = null
+        waitsForPromise ->
+          workspace.open('sample.js').then (e) -> editor = e
+
+        runs ->
+          expect(editor.displayBuffer.largeFileMode).toBe true
+
     describe "when passed a path that matches a custom opener", ->
       it "returns the resource returned by the custom opener", ->
         fooOpener = (pathToOpen, options) -> {foo: pathToOpen, options} if pathToOpen?.match(/\.foo/)


Replace ‘editor-created’ event with ::onDidAddTextEditor

--- spec/workspace-spec.coffee
+++ spec/workspace-spec.coffee
@@ -203,17 +203,17 @@
           workspace.open("bar://baz").then (item) ->
             expect(item).toEqual { bar: "bar://baz" }

-    it "emits an 'editor-created' event", ->
+    it "notifies ::onDidAddTextEditor observers", ->
       absolutePath = require.resolve('./fixtures/dir/a')
       newEditorHandler = jasmine.createSpy('newEditorHandler')
-      workspace.on 'editor-created', newEditorHandler
+      workspace.onDidAddTextEditor newEditorHandler

       editor = null
       waitsForPromise ->
         workspace.open(absolutePath).then (e) -> editor = e

       runs ->
-        expect(newEditorHandler).toHaveBeenCalledWith editor
+        expect(newEditorHandler.argsForCall[0][0].textEditor).toBe editor

   describe "::reopenItem()", ->
     it "opens the uri associated with the last closed pane that isn't currently open", ->


more overlay specs

more overlay specs

--- test/unit/layersDirective/layersDirective.overlays.markersSpec.coffee
+++ test/unit/layersDirective/layersDirective.overlays.markersSpec.coffee
@@ -87,6 +87,38 @@
                     expect(map.hasLayer(markerToCheck)).toBe true
                     expect(layers.overlays.cars.hasLayer(markerToCheck)).toBe true
                     done()
+
+        it 'should check for a marker in a wrong layer group', ->
+            angular.extend scope,
+                layers:
+                    baselayers: osm:
+                        name: 'OpenStreetMap'
+                        type: 'xyz'
+                        url: 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
+                        layerOptions:
+                            subdomains: [
+                                'a'
+                                'b'
+                                'c'
+                            ]
+                            attribution: '© OpenStreetMap contributors'
+                            continuousWorld: true
+                    overlays:
+                        cars:
+                            name: 'cars'
+                            type: 'group'
+                            visible: true
+                markers:
+                    bikes:
+                        m1:
+                            lat: 1.2
+                            lng: 0.3
+
+            element = angular.element('<leaflet layers="layers" markers="markers" markers-nested="true"></leaflet>')
+            element = $compile(element)(scope)
+            leafletData.getMarkers().then (markers) ->
+                expect(Object.keys(markers).length).toEqual 0
+

     describe 'marker', ->


Refactor piece a bunch

Refactor piece a bunch

--- coffee/models/piece.coffee
+++ coffee/models/piece.coffee
@@ -43,67 +43,70 @@
     @width = 0
     @height = 0
     unless @storage
-      piecenum = Math.floor(Math.random() * 7)
-      @width = pieces[piecenum][0].length
-      @height = pieces[piecenum].length
+      index = Math.floor(Math.random() * 7)
+      piece = pieces[index]
+      @height = piece.length
+      @width = piece[0].length
+
+      alpha = (if peer then 200 else 255)
+
       @storage = new Array(@width * @height * 4)
       for x in [0...@width] by 1
         for y in [0...@height] by 1
-          @storage[(x + y * @width) * 4] = colors[piecenum].r
-          @storage[(x + y * @width) * 4 + 1] = colors[piecenum].g
-          @storage[(x + y * @width) * 4 + 2] = colors[piecenum].b
-          @storage[(x + y * @width) * 4 + 3] = (if pieces[piecenum][y][x] then (if peer then 200 else 255) else 0)
+          offset = (x + y * @width) * 4
+          {r, g, b} = colors[index]
+          @storage[offset] = r
+          @storage[offset + 1] = g
+          @storage[offset + 2] = b
+          @storage[offset + 3] = (if piece[y][x] then alpha else 0)

   rotate: (times) ->
-    newstorage = (x for x in @storage)
-    newwidth = @width
-    newheight = @height
+    storage = (x for x in @storage)
+    width = @width
+    height = @height
     for i in [0...times] by 1
-      tmpstorage = new Array(newstorage.length)
-      for x in [0...newwidth] by 1
-        for y in [0...newheight] by 1
-          tmpstorage[(newheight - y - 1 + x * newheight) * 4] = newstorage[(x + y * newwidth) * 4]
-          tmpstorage[(newheight - y - 1 + x * newheight) * 4 + 1] = newstorage[(x + y * newwidth) * 4 + 1]
-          tmpstorage[(newheight - y - 1 + x * newheight) * 4 + 2] = newstorage[(x + y * newwidth) * 4 + 2]
-          tmpstorage[(newheight - y - 1 + x * newheight) * 4 + 3] = newstorage[(x + y * newwidth) * 4 + 3]
-      newstorage = (x for x in tmpstorage)
-      tmp = newwidth
-      newwidth = newheight
-      newheight = tmp
+      targetStorage = new Array(storage.length)
+      for x in [0...width] by 1
+        for y in [0...height] by 1
+          sourceOffset = (x + y * width) * 4
+          targetOffset = (height - y - 1 + x * height) * 4
+          targetStorage[targetOffset] = storage[sourceOffset]
+          targetStorage[targetOffset + 1] = storage[sourceOffset + 1]
+          targetStorage[targetOffset + 2] = storage[sourceOffset + 2]
+          targetStorage[targetOffset + 3] = storage[sourceOffset + 3]

-    deltaX = @width - newwidth
-    deltaY = 0 # Math.floor(newheight / 2 - @height / 2)
+      storage = (x for x in targetStorage)
+      tmp = width
+      width = height
+      height = tmp

-    collide = -> Tetrus.get('controllers.game').game.collide(arguments...)
+    deltaX = @width - width
+    deltaY = 0 # Math.floor(height / 2 - @height / 2)

-    unless collide(newstorage, @position.x + deltaX, @position.y + deltaY, newwidth, newheight)
-      @storage = newstorage
-      @width = newwidth
-      @height = newheight
-      @position.x += deltaX
-      @position.y += deltaY
+    game = Tetrus.get('controllers.game').game
+
+    setStorage = (dx, dy) =>
+      @storage = storage
+      @width = width
+      @height = height
+      @position.x += deltaX + dx
+      @position.y += deltaY + dy
+
+    unless game.collide(storage, @position.x + deltaX, @position.y + deltaY, width, height)
+      setStorage(0, 0)
     else
       radius = 2
       for dy in [0..radius] by 1
         for dx in [0..radius] by 1
           continue if dx == 0 and dy == 0
-
-          setStorage = (dx, dy) ->
-            @storage = newstorage
-            @width = newwidth
-            @height = newheight
-            @position.x += deltaX + dx
-            @position.y += deltaY + dy
-            return
-
-          unless collide(newstorage, @position.x + deltaX + dx, @position.y + deltaY - dy, newwidth, newheight)
+          unless game.collide(storage, @position.x + deltaX + dx, @position.y + deltaY - dy, width, height)
             return setStorage(dx, -dy)
-          unless collide(newstorage, @position.x + deltaX - dx, @position.y + deltaY - dy, newwidth, newheight)
+          unless game.collide(storage, @position.x + deltaX - dx, @position.y + deltaY - dy, width, height)
             return setStorage(-dx, -dy)
           unless dy == 0
-            unless collide(newstorage, @position.x + deltaX + dx, @position.y + deltaY + dy, newwidth, newheight)
+            unless game.collide(storage, @position.x + deltaX + dx, @position.y + deltaY + dy, width, height)
               return setStorage(dx, dy)
-            unless collide(newstorage, @position.x + deltaX - dx, @position.y + deltaY + dy, newwidth, newheight)
+            unless game.collide(storage, @position.x + deltaX - dx, @position.y + deltaY + dy, width, height)
               return setStorage(-dx, dy)

   apply: (piece) ->
@@ -111,3 +114,4 @@
     @position = piece.position
     @width = piece.width
     @height = piece.height
+


coupling stiffness to 30

coupling stiffness to 30

changed coupling stiffness for consistency please work

--- src/racer/RacerDefs.coffee
+++ src/racer/RacerDefs.coffee
@@ -45,7 +45,7 @@
       'stiffness': 20
       'damping': 1
     @coupling =
-      'stiffness': 20
+      'stiffness': 30
       'damping': 0.5

 RacerDefs = {}


Confirmation dialog added to web form.

Confirmation dialog added to web form.

--- web/coffee/account.coffee
+++ web/coffee/account.coffee
@@ -2,11 +2,11 @@
   e.preventDefault()
   apiCall "POST", "/api/user/update_password", $("#password-update-form").serializeObject()
   .done (data) ->
-    switch data['status']
+    switch data['status']
       when 1
-        ga('send', 'event', 'Authentication', 'UpdatePassword', 'Success')
+        ga('send', 'event', 'Authentication', 'UpdatePassword', 'Success')
       when 0
-        ga('send', 'event', 'Authentication', 'UpdatePassword', 'Failure::' + data.message)
+        ga('send', 'event', 'Authentication', 'UpdatePassword', 'Failure::' + data.message)
     apiNotify data, "/account"

 resetPassword = (e) ->
@@ -17,6 +17,18 @@
   .done (data) ->
     ga('send', 'event', 'Authentication', 'ResetPassword', 'Success')
     apiNotify data, "/login"
+
+disableAccount = (e) ->
+  e.preventDefault()
+  confirmDialog("This will disable your account, drop you from your team, and prevent you from playing!", "Disable Account Confirmation", "Disable Account", "Cancel",
+  () ->
+    form = $("#disable-account-form").serializeObject()
+    apiCall "POST", "/api/user/disable_account", form
+    .done (data) ->
+      ga('send', 'event', 'Authentication', 'DisableAccount', 'Success')
+      apiNotify data, "/")
+
 $ ->
   $("#password-update-form").on "submit", updatePassword
   $("#password-reset-form").on "submit", resetPassword
+  $("#disable-account-form").on "submit", disableAccount


Adds support for json-api in pagination

Adds support for json-api in pagination

--- app/mixins/paginate.coffee
+++ app/mixins/paginate.coffee
@@ -8,6 +8,7 @@
   version: 0
   extraQueryStrings: ""
   limit: ENV.paginate.perPageLimit
+  isJsonApiPagination: false

   versionIncrementer: ->
     @incrementProperty "version"
@@ -25,9 +26,16 @@
   objects: ( ->
     window.scrollTo 0, 0
     that = @
-    query =
-      limit: @get "limit"
-      offset: @get "offset"
+    if that.get 'isJsonApiPagination'
+      query_limit = @get "limit"
+      query_offset = @get "offset"
+      query =
+        'page[limit]': @get "limit"
+        'page[offset]': query_limit * query_offset
+    else
+      query =
+        limit: @get "limit"
+        offset: @get "offset"
     extraQueryStrings = @get "extraQueryStrings"
     if !Ember.isEmpty extraQueryStrings
       extraQueries = JSON.parse extraQueryStrings
@@ -36,8 +44,11 @@
     targetObject = @get "targetObject"
     objects = @get('store').query targetObject, query
     objects.then (result) ->
-      that.set "meta", result.meta
-      # that.set "meta", total: 200
+      meta = result.meta
+      if result.links and result.meta.pagination
+        meta.total = result.meta.pagination.count
+        that.set 'isJsonApiPagination', true
+      that.set "meta", meta
     objects
   ).property  "version"


enhance: show cvss only if `cvssVersion` is 3

--- app/models/analysis.coffee
+++ app/models/analysis.coffee
@@ -14,6 +14,7 @@
   vulnerability: DS.belongsTo 'vulnerability'
   cvssBase: DS.attr 'number'
   cvssVector: DS.attr 'string'
+  cvssVersion: DS.attr 'number'

   cvssMetricsHumanized: (->
     cvssVector = @get "cvssVector"
@@ -27,7 +28,7 @@
   tHigh: t("high")
   tCritical: t("critical")

-  hascvccBase: Ember.computed.gt 'cvssBase', -1.0
+  hascvccBase: Ember.computed.equal 'cvssVersion', 3

   isScanning: ( ->
     risk = @get "risk"


generators

generators

--- app/models/employee.coffee
+++ app/models/employee.coffee
@@ -39,7 +39,7 @@
     when: equal "model.role", "worker"
     ->
       @set "role", "admin"
-      @save()
+      yield return @save()

   demoteToWorker: action "click",
     label: "Demote to Warehouse Worker"
@@ -48,6 +48,6 @@
     when: equal "model.role", "admin"
     ->
       @set "role", "worker"
-      @save()
+      yield return @save()

 `export default Model`


[#2] Don't display zero minutes

[#2] Don't display zero minutes

--- bin/tourney-time.coffee
+++ bin/tourney-time.coffee
@@ -14,10 +14,15 @@
 hours = Math.floor(timeNeededMinutes / 60)
 minutes = timeNeededMinutes % 60

+time = ""
+time += "#{hours} hours" if hours
+time += ', ' if hours and minutes
+time += "#{minutes} minutes" if minutes
+
 console.log """For #{teams} teams
                Playing #{time} minute games
                with #{rest} minute breaks in between games
                on #{areas} playing area(s)
                you'll play a #{tourneySchedule.type} tournament with #{tourneySchedule.games + playoffGames} total games
                #{tourneySchedule.games} tourney games and #{playoffGames} playoff games
-               which will take #{hours} hours, #{minutes} minutes """
+               which will take #{time} """


Adding killswitch for forum poller

Adding killswitch for forum poller

--- scripts/stackoverflow-poller.coffee
+++ scripts/stackoverflow-poller.coffee
@@ -10,6 +10,7 @@
 PROD_MODE        = process.env.NODE_ENV is 'production'
 POLL_INTERVAL    = 1000 * 60 * 180
 ANNOUNCE_ROOMS   = []
+ENABLED          = process.env.FORUM_POLL_ENABLED || false

 do =>
   rooms = process.env.FORUM_POLL_ANNOUNCE_ROOMS || process.env.HUBOT_IRC_ROOMS || ''
@@ -75,6 +76,8 @@
       ANNOUNCE_ROOMS.forEach (room) => @robot.messageRoom room, messages.join("\r\n")

 module.exports = (robot) ->
+  return unless ENABLED
+
   poller = new SOPoller(robot)

   # Start polling


fix default value choice

fix default value choice

--- BackofficeBundle/Resources/public/coffee/addPrototype.coffee
+++ BackofficeBundle/Resources/public/coffee/addPrototype.coffee
@@ -102,7 +102,6 @@

     # Display the input in the page before the add button
     @settings.addButtonContainer.before newPrototype
-    newPrototype.find("input").click()

     @createRemoveButton newPrototype
     # increase the index with one for the next item


Add GenericPage#include

Add GenericPage#include

--- vendor/assets/javascripts/fustrate/generic_page.coffee
+++ vendor/assets/javascripts/fustrate/generic_page.coffee
@@ -4,7 +4,33 @@
     @addEventListeners()
     @initialize()

-  addEventListeners: ->
+  include: (concern) =>
+    instance = new concern
+
+    for own key, value of instance.constructor
+      continue if key in ['included', 'initialize', 'strings']
+
+      @constructor[key] = value unless @constructor[key]
+
+    # Assign properties to the prototype
+    for key, value of concern.prototype
+      continue if key in ['included', 'initialize', 'strings']
+
+      @[key] = value.bind(@) unless @[key]
+
+    instance.included?.apply(@)
+
+    return unless instance.constructor.strings
+
+    @constructor.strings ?= {}
+
+    $.extend true, @constructor.strings, instance.constructor.strings
+
+  addEventListeners: =>
+    super
+
+    for name, func of @
+      func.apply(@) if /^add.+EventListeners$/.test name

   # Once the interface is loaded and the event listeners are active, run any
   # other tasks.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/floating_text_effect.coffee
+++ src/floating_text_effect.coffee
@@ -2,7 +2,7 @@
 FloatingTextEffect is a simple subclass of `TextEffect`. It provides some defaults
 to move the text upward and fade it out over 0.5 seconds.

-    # adds a
+    # adds a f
     engine.add 'FloatingTextEffect'
       x: 50
       y: 50


Add a possibility to query the entries of a specific day.

--- src/scripts/harvest.coffee
+++ src/scripts/harvest.coffee
@@ -16,7 +16,7 @@
 #   hubot forget my harvest account - Make hubot forget your Harvest credentials again
 #   hubot start harvest at <project>/<task>: <notes> - Start a Harvest timer at a given project-task combination
 #   hubot stop harvest [at project/task] - Stop the most recent Harvest timer or the one for the given project-task combination.
-#   hubot daily harvest [of <user>] - Show a user's Harvest timers for today (or yours, if noone is specified)
+#   hubot daily harvest [of <user>] [at yyyy-mm-dd] - Show a user's Harvest timers for today (or yours, if noone is specified) or a specific day
 #   hubot list harvest tasks [of <user>] - Show the Harvest project-task combinations available to a user (or you, if noone is specified)
 #   hubot is harvest down/up - Check if the Harvest API is reachable.
 #
@@ -52,9 +52,11 @@
 #     stops the first active timer it can find. The project and
 #     task arguments may be abbreviated as with start.
 #
-#   hubot daily harvest [of <user>]
+#   hubot daily harvest [of <user>] [at yyyy-mm-dd]
 #     Hubot responds with your/a specific user's entries
-#     for today.
+#     for the given date; if no date is given, assumes today.
+#     If user is ommitted, you are assumed; if both the user and
+#     the date are ommited, your entries for today will be displayed.
 #
 #   hubot list harvest tasks [of <user>]
 #     Gives you a list of all project/task combinations available
@@ -157,22 +159,41 @@
     msg.reply "Okay, I erased your credentials from my memory."

   # Retrieve your or a specific user's timesheet for today.
-  robot.respond /daily harvest( of (.+))?/i, (msg) ->
+  robot.respond /daily harvest( of (\w+))?( at (\d{4})-(\d{2})-(\d{2}))?/i, (msg) ->
     unless user = check_user(robot, msg, msg.match[2])
       return
     harvest = new HarvestService(user.harvest_account)

+    if msg.match[3]
+      target_date = new Date(parseInt(msg.match[4]), parseInt(msg.match[5] - 1), parseInt(msg.match[6])) # Month starts at 0
+
     try
-      harvest.daily msg, (status, body) ->
-        if 200 <= status <= 299
-          msg.reply "Your entries for today, #{user.name}:"
-          for entry in body.day_entries
-            if entry.ended_at == ""
-              msg.reply "• #{entry.project} (#{entry.client}) → #{entry.task} <#{entry.notes}> [running since #{entry.started_at} (#{entry.hours}h)]"
+      if target_date
+        harvest.daily_at msg, target_date, (status, body) ->
+          if 200 <= status <= 299
+            if body.day_entries.length == 0
+              msg.reply "#{user.name} has no entries on #{target_date}."
             else
-              msg.reply "• #{entry.project} (#{entry.client}) → #{entry.task} <#{entry.notes}> [#{entry.started_at} – #{entry.ended_at} (#{entry.hours}h)]"
-        else
-          msg.reply "Request failed with status #{status}."
+              msg.reply "#{user.name}'s entries on #{target_date}:"
+
+            for entry in body.day_entries
+              if entry.ended_at == ""
+                msg.reply "• #{entry.project} (#{entry.client}) → #{entry.task} <#{entry.notes}> [running since #{entry.started_at} (#{entry.hours}h)]"
+              else
+                msg.reply "• #{entry.project} (#{entry.client}) → #{entry.task} <#{entry.notes}> [#{entry.started_at} – #{entry.ended_at} (#{entry.hours}h)]"
+          else
+            msg.reply "Request failed with status #{status}."
+      else
+        harvest.daily msg, (status, body) ->
+          if 200 <= status <= 299
+            msg.reply "Your entries for today, #{user.name}:"
+            for entry in body.day_entries
+              if entry.ended_at == ""
+                msg.reply "• #{entry.project} (#{entry.client}) → #{entry.task} <#{entry.notes}> [running since #{entry.started_at} (#{entry.hours}h)]"
+              else
+                msg.reply "• #{entry.project} (#{entry.client}) → #{entry.task} <#{entry.notes}> [#{entry.started_at} – #{entry.ended_at} (#{entry.hours}h)]"
+          else
+            msg.reply "Request failed with status #{status}."
     catch error
       msg.reply("Fatal error: #{error}")

@@ -289,6 +310,11 @@
   # Issues /daily to the Harvest API.
   daily: (msg, callback) ->
     this.request(msg).path("/daily").get() (err, res, body) ->
+      callback res.statusCode, JSON.parse(body)
+
+  # Issues /daily/<dayofyear>/<year> to the Harvest API.
+  daily_at: (msg, date, callback) ->
+    this.request(msg).path("/daily/#{this.day_of_year(date)}/#{date.getFullYear()}").get() (err, res, body) ->
       callback res.statusCode, JSON.parse(body)

   # Issues /daily/add to the Harvest API to add a new timer
@@ -429,3 +455,10 @@

         # Execute the callback with the result
         callback found_entry
+
+  # Takes a Date object and figures out which day in its
+  # year it represents and returns that one. Leap years
+  # are honoured.
+  day_of_year: (date) ->
+    start = new Date(date.getFullYear(), 0, 0)
+    return Math.ceil((date - start) / 86400000)


Periodically check if Harvest available. Also add a command to trigger this manually.

--- src/scripts/harvest.coffee
+++ src/scripts/harvest.coffee
@@ -18,6 +18,7 @@
 #   hubot stop harvest [at project/task] - Stop the most recent Harvest timer or the one for the given project-task combination.
 #   hubot daily harvest [of <user>] - Show a user's Harvest timers for today (or yours, if noone is specified)
 #   hubot list harvest tasks [of <user>] - Show the Harvest project-task combinations available to a user (or you, if noone is specified)
+#   hubot is harvest down/up - Check if the Harvest API is reachable.
 #
 # Notes:
 #   All commands and command arguments are case-insenitive. If you work
@@ -61,6 +62,8 @@
 #
 # Author:
 #   Quintus @ Asquera
+#
+http = require("http")

 unless process.env.HUBOT_HARVEST_SUBDOMAIN
   console.log "Please set HUBOT_HARVEST_SUBDOMAIN in the environment to use the harvest plugin script."
@@ -91,8 +94,45 @@

   return user

+# Issues an empty GET request to harvest to test whether the service is
+# available at the moment. The callback gets passed an exception object
+# describing the connection error; if everything is fine it gets passed
+# null.
+check_harvest_down = (callback) ->
+  opts =
+    headers:
+      "Content-Type": "application/json"
+      "Accept": "application/json"
+    method: "GET"
+    host: "#{process.env.HUBOT_HARVEST_SUBDOMAIN}.harvestapp.com"
+    port: 80
+    path: "/account/who_am_i"
+  req = http.request opts, (response) ->
+    callback null
+  req.on "error", (error) ->
+    callback error
+  req.setTimeout 5000, ->
+    req.destroy() # Cancel the request
+    callback "Connection timeout"
+  req.end()
+
 ### Definitions for hubot ###
 module.exports = (robot) ->
+
+  # Periodically check the Harvest service for availability
+  cb = ->
+    check_harvest_down (error) ->
+      if (error)
+        robot.send "broadcast", "Harvest appears to be down; exact error is: #{error}"
+  setInterval(cb, 600000) # 10 Minutes in milliseconds
+
+  # Check if Harvest is available.
+  robot.respond /is harvest (down|up)/i, (msg) ->
+    check_harvest_down (error) ->
+      if error
+        msg.reply("Harvest is down; exact error: #{error}")
+      else
+        msg.reply("Harvest is up.")

   # Provide facility for saving the account credentials.
   robot.respond /remember my harvest account (.+) with password (.+)/i, (msg) ->


Workspace: Added history reference and a method to write that history ref.

--- client/CollaborativeWorkspace/collaborativeworkspace.coffee
+++ client/CollaborativeWorkspace/collaborativeworkspace.coffee
@@ -25,6 +25,7 @@
     @sessionKey   = @getOptions().sessionKey or @createSessionKey()
     @workspaceRef = @firepadRef.child @sessionKey
     @broadcastRef = @workspaceRef.child "broadcast"
+    @historyRef   = @workspaceRef.child "history"

   bindRemoteEvents: ->
     @workspaceRef.once "value", (snapshot) =>
@@ -299,6 +300,14 @@
       delegate  : this
     }

+  addToHistory: (data) ->
+    target = @historyRef.child Date.now()
+    if typeof data is "string"
+      target.set message: data.replace "$0", KD.nick()
+    else
+      data.message = data.message.replace "$0", KD.nick()  if data.message
+      target.set data
+
   broadcastMessage: (details) ->
     @broadcastRef.set
       data       :


Workspace: User presence on history node.

--- client/CollaborativeWorkspace/collaborativeworkspace.coffee
+++ client/CollaborativeWorkspace/collaborativeworkspace.coffee
@@ -46,6 +46,8 @@
       @userRef = @workspaceRef.child("users").child @nickname
       @userRef.set "online"
       @userRef.onDisconnect().set "offline"
+      record = if isOldSession then "$0 joined the session" else "$0 started the session"
+      @addToHistory record

       if @amIHost()
         @workspaceRef.onDisconnect().remove()
@@ -62,10 +64,14 @@
     @workspaceRef.child("users").on "child_changed", (snapshot) =>
       name = snapshot.name()
       if @amIHost() and snapshot.val() is "offline"
+        message = "#{name} has left the session"
+
         @broadcastMessage
-          title     : "#{name} has left the session"
+          title     : message
           cssClass  : "error"
           sender    : name
+
+        @addToHistory message

     @workspaceRef.on "child_removed", (snapshot) =>
       return  if @disconnectedModal


InviteSomeoneView: Created modal buttons for cancel and invite.

--- client/admin/lib/views/invitations/invitesomeoneview.coffee
+++ client/admin/lib/views/invitations/invitesomeoneview.coffee
@@ -1,8 +1,10 @@
-kd                      = require 'kd'
-KDView                  = kd.View
-KDButtonView            = kd.ButtonView
-KDCustomHTMLView        = kd.CustomHTMLView
-InvitationInputView     = require './invitationinputview'
+kd                  = require 'kd'
+remote              = require('app/remote').getInstance()
+KDView              = kd.View
+KDButtonView        = kd.ButtonView
+KDCustomHTMLView    = kd.CustomHTMLView
+KDNotificationView  = kd.NotificationView
+InvitationInputView = require './invitationinputview'

 module.exports = class InviteSomeoneView extends KDView
@@ -19,6 +21,7 @@
     @addSubView @inputWrapper = new KDCustomHTMLView cssClass: 'input-wrapper'
     @createInvitationView no
     @createAddMoreButton()
+    @createMainButtons()

   createInvitationView: (cancellable) ->
@@ -40,6 +43,18 @@
       callback : @bound 'createInvitationView'

+  createMainButtons: ->
+
+    @addSubView new KDButtonView
+      title    : 'CANCEL'
+      cssClass : 'solid medium cancel'
+      callback : => @emit 'InvitationViewCancelled'
+
+    @addSubView new KDButtonView
+      title    : 'INVITE MEMBERS'
+      cssClass : 'solid medium green invite-members'
+      callback : @bound 'inviteMembers'
+
   createInformationView: ->

     @addSubView new KDCustomHTMLView


Fix sceDelegateProvider error

Fix sceDelegateProvider error

--- app/scripts/app/app.coffee
+++ app/scripts/app/app.coffee
@@ -9,8 +9,8 @@
 ]

 module.config [
-	'$locationProvider', '$stateProvider', '$dialogProvider', 'AngularyticsProvider', '$httpProvider'
-	(location, state, $dialogProvider, analytics, $httpProvider) ->
+	'$locationProvider', '$stateProvider', '$dialogProvider', 'AngularyticsProvider', '$httpProvider', '$sceDelegateProvider'
+	(location, state, $dialogProvider, analytics, $httpProvider, $sceDelegateProvider) ->

 		$httpProvider.interceptors.push [
 			->
@@ -18,6 +18,11 @@
 					return config if !(/^\/?templates\//.test config.url)
 					config.url += '?' + Math.ceil(Math.random() * 1000000)
 					return config
+		]
+
+		$sceDelegateProvider.resourceUrlWhitelist [
+			'self',
+			'/templates/directives/*'
 		]

 		analytics.setEventHandlers ['Google']


Improve performance for long line. Related to #536

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -18,6 +18,9 @@
         limit: 40 # How many results we display per page // Final for now
         line_height: 13 # Define the height of a line (used for a line is too long)
         size_history: 100
+
+        max_size_stack: 100 # If the stack of the query (including function, string, object etc. is greater than @max_size_stack, we stop parsing the query
+        max_size_query: 1000 # If the query is more than 1000 char, we don't show suggestion (codemirror doesn't highlight/parse if the query is more than 1000 characters too

         events:
             'mouseup .CodeMirror': 'handle_click'
@@ -309,12 +312,12 @@
             @codemirror = CodeMirror.fromTextArea document.getElementById('input_query'),
                 mode:
                     name: 'javascript'
-                    json: true
                 onKeyEvent: @handle_keypress
                 lineNumbers: true
                 lineWrapping: true
                 matchBrackets: true
                 tabSize: 2
+
             @codemirror.on 'blur', @on_blur
             @codemirror.on 'gutterClick', @handle_gutter_click

@@ -518,6 +521,11 @@

             if event?.which isnt 9 # has to be before create_suggestion()
                 @cursor_for_auto_completion = @codemirror.getCursor()
+
+            # The expensive operations are coming. If the query is too long, we just don't parse the query
+            if @codemirror.getValue().length > @max_size_query
+                return false
+
             # We just look at key up so we don't fire the call 3 times
             if event?.type? and event.type isnt 'keyup' and event.which isnt 9 and event.type isnt 'mouseup'
                 return false
@@ -570,11 +578,12 @@

             # If we are in the middle of a function (text after the cursor - that is not an element in @char_breakers), we just show a description, not a suggestion
             result_non_white_char_after_cursor = @regex.get_first_non_white_char.exec(query_after_cursor)
+
             if result_non_white_char_after_cursor isnt null and not(result_non_white_char_after_cursor[1]?[0] of @char_breakers)
                 result.status = 'break_and_look_for_description'
                 @hide_suggestion()
             else
-                result_last_char_is_white = @regex.last_char_is_white.exec(query_before_cursor)
+                result_last_char_is_white = @regex.last_char_is_white.exec(query_before_cursor[query_before_cursor.length-1])
                 if result_last_char_is_white isnt null
                     result.status = 'break_and_look_for_description'
                     @hide_suggestion()
@@ -654,11 +663,11 @@
                 else
                     return ''

-
         # We build a stack of the query.
         # Chained functions are in the same array, arguments/inner queries are in a nested array
         # element.type in ['string', 'function', 'var', 'separator', 'anonymous_function', 'object', 'array_entry', 'object_key' 'array']
         extract_data_from_query: (args) =>
+            size_stack = args.size_stack
             query = args.query
             context = if args.context? then DataUtils.deep_copy(args.context) else {}
             position = args.position
@@ -685,6 +694,9 @@
                             element.name = query.slice start, i+1
                             element.complete = true
                             stack.push element
+                            size_stack++
+                            if size_stack > @max_size_stack
+                                return null
                             element =
                                 type: null
                                 context: context
@@ -752,6 +764,9 @@
                                 element.complete = true
                                 to_skip = result_regex[0].length-1
                                 stack.push element
+                                size_stack++
+                                if size_stack > @max_size_stack
+                                    return null
                                 element =
                                     type: null
                                     context: context
@@ -806,6 +821,9 @@
                                         element.type = 'var'
                                         element.name = result_regex[0].slice(0, position_opening_parenthesis)
                                         stack.push element
+                                        size_stack++
+                                        if size_stack > @max_size_stack
+                                            return null
                                         element =
                                             type: 'function'
                                             name: '('
@@ -830,6 +848,9 @@
                                 element.complete = true
                                 to_skip = element.name.length-1+new_start-i
                                 stack.push element
+                                size_stack++
+                                if size_stack > @max_size_stack
+                                    return null
                                 element =
                                     type: null
                                     context: context
@@ -846,6 +867,9 @@
                                     type: 'separator'
                                     complete: true
                                     name: query.slice i, result_regex[0].length
+                                size_stack++
+                                if size_stack > @max_size_stack
+                                    return null
                                 element =
                                     type: null
                                     context: context
@@ -863,6 +887,9 @@
                                     type: 'separator'
                                     complete: true
                                     name: query.slice i, result_regex[0].length
+                                size_stack++
+                                if size_stack > @max_size_stack
+                                    return null
                                 element =
                                     type: null
                                     context: context
@@ -887,6 +914,9 @@
                                 complete: true
                                 name: query.slice i, result_regex[0].length
                                 position: position+i
+                            size_stack++
+                            if size_stack > @max_size_stack
+                                return null
                             element =
                                 type: null
                                 context: context
@@ -904,11 +934,17 @@
                                     stack_stop_char.pop()
                                     if stack_stop_char.length is 0
                                         element.body = @extract_data_from_query
+                                            size_stack: size_stack
                                             query: query.slice body_start, i
                                             context: element.context
                                             position: position+body_start
+                                        if element.body is null
+                                            return null
                                         element.complete = true
                                         stack.push element
+                                        size_stack++
+                                        if size_stack > @max_size_stack
+                                            return null
                                         element =
                                             type: null
                                             context: context
@@ -923,11 +959,17 @@
                                     stack_stop_char.pop()
                                     if stack_stop_char.length is 0
                                         element.body = @extract_data_from_query
+                                            size_stack: size_stack
                                             query: query.slice body_start, i
                                             context: element.context
                                             position: position+body_start
+                                        if element.body
+                                            return null
                                         element.complete = true
                                         stack.push element
+                                        size_stack++
+                                        if size_stack > @max_size_stack
+                                            return null
                                         element =
                                             type: null
                                             context: context
@@ -941,11 +983,17 @@
                                     stack_stop_char.pop()
                                     if stack_stop_char.length is 0
                                         element.body = @extract_data_from_query
+                                            size_stack: size_stack
                                             query: query.slice start+element.name.length, i
                                             context: element.context
                                             position: position+start+element.name.length
+                                        if element.body is null
+                                            return null
                                         element.complete = true
                                         stack.push element
+                                        size_stack++
+                                        if size_stack > @max_size_stack
+                                            return null
                                         element =
                                             type: null
                                             context: context
@@ -964,15 +1012,19 @@
                                     if stack_stop_char.length is 0
                                         # We just reach a }, it's the end of the object
                                         if element.next_key?
+                                            body = @extract_data_from_query
+                                                size_stack: size_stack
+                                                query: query.slice element.current_value_start, i
+                                                context: element.context
+                                                position: position+element.current_value_start
+                                            if body is null
+                                                return null
                                             new_element =
                                                 type: 'object_key'
                                                 key: element.next_key
                                                 key_complete: true
                                                 complete: false
-                                                body: @extract_data_from_query
-                                                    query: query.slice element.current_value_start, i
-                                                    context: element.context
-                                                    position: position+element.current_value_start
+                                                body: body
                                             element.body[element.body.length-1] = new_element
                                         element.next_key = null # No more next_key
                                         element.complete = true
@@ -981,6 +1033,9 @@
                                         # TODO show error once brackets/ace will be used

                                         stack.push element
+                                        size_stack++
+                                        if size_stack > @max_size_stack
+                                            return null
                                         element =
                                             type: null
                                             context: context
@@ -995,6 +1050,9 @@
                                         key_complete: true
                                     if element.body.length is 0
                                         element.body.push new_element
+                                        size_stack++
+                                        if size_stack > @max_size_stack
+                                            return null
                                     else
                                         element.body[element.body.length-1] = new_element
                                     element.next_key = query.slice element.current_key_start, i
@@ -1002,14 +1060,18 @@
                             else
                                 result_regex = @regex.comma.exec query.slice i
                                 if stack_stop_char.length is 1 and result_regex isnt null #We reached the end of a value
+                                    body = @extract_data_from_query
+                                        size_stack: size_stack
+                                        query: query.slice element.current_value_start, i
+                                        context: element.context
+                                        position: element.current_value_start
+                                    if body is null
+                                        return null
                                     new_element =
                                         type: 'object_key'
                                         key:  element.next_key
                                         key_complete: true
-                                        body: @extract_data_from_query
-                                            query: query.slice element.current_value_start, i
-                                            context: element.context
-                                            position: element.current_value_start
+                                        body: body
                                     element.body[element.body.length-1] = new_element
                                     to_skip = result_regex[0].length-1
                                     element.next_key = null
@@ -1023,27 +1085,41 @@
                                     stack_stop_char.pop()
                                     if stack_stop_char.length is 0
                                         # We just reach a ], it's the end of the object
+                                        body = @extract_data_from_query
+                                            size_stack: size_stack
+                                            query: query.slice entry_start, i
+                                            context: element.context
+                                            position: position+entry_start
+                                        if body is null
+                                            return null
                                         new_element =
                                             type: 'array_entry'
                                             complete: true
-                                            body: @extract_data_from_query
-                                                query: query.slice entry_start, i
-                                                context: element.context
-                                                position: position+entry_start
+                                            body: body
                                         if new_element.body.length > 0
                                             element.body.push new_element
+                                            size_stack++
+                                            if size_stack > @max_size_stack
+                                                return null
                                         continue

                             if stack_stop_char.length is 1 and char is ','
+                                body = @extract_data_from_query
+                                    size_stack: size_stack
+                                    query: query.slice entry_start, i
+                                    context: element.context
+                                    position: position+entry_start
+                                if body is null
+                                    return null
                                 new_element =
                                     type: 'array_entry'
                                     complete: true
-                                    body: @extract_data_from_query
-                                        query: query.slice entry_start, i
-                                        context: element.context
-                                        position: position+entry_start
+                                    body: body
                                 if new_element.body.length > 0
                                     element.body.push new_element
+                                    size_stack++
+                                    if size_stack > @max_size_stack
+                                        return null
                                 entry_start = i+1

             # We just reached the end, let's try to find the type of the incomplete element
@@ -1051,19 +1127,28 @@
                 element.complete = false
                 if element.type is 'function'
                     element.body = @extract_data_from_query
+                        size_stack: size_stack
                         query: query.slice start+element.name.length
                         context: element.context
                         position: position+start+element.name.length
+                    if element.body is null
+                        return null
                 else if element.type is 'anonymous_function'
                     element.body = @extract_data_from_query
+                        size_stack: size_stack
                         query: query.slice body_start
                         context: element.context
                         position: position+body_start
+                    if element.body is null
+                        return null
                 else if element.type is 'loop'
                     element.body = @extract_data_from_query
+                        size_stack: size_stack
                         query: query.slice body_start
                         context: element.context
                         position: position+body_start
+                    if element.body is null
+                        return null
                 else if element.type is 'string'
                     element.name = query.slice start
                 else if element.type is 'object'
@@ -1074,30 +1159,47 @@
                             key_complete: false
                             complete: false
                         element.body.push new_element # They key was not defined, so we add a new element
+                        size_stack++
+                        if size_stack > @max_size_stack
+                            return null
                         element.next_key = query.slice element.current_key_start
                     else
+                        body = @extract_data_from_query
+                            size_stack: size_stack
+                            query: query.slice element.current_value_start
+                            context: element.context
+                            position: position+element.current_value_start
+                        if body is null
+                            return null
                         new_element =
                             type: 'object_key'
                             key: element.next_key
                             key_complete: true
                             complete: false
-                            body: @extract_data_from_query
-                                query: query.slice element.current_value_start
-                                context: element.context
-                                position: position+element.current_value_start
+                            body: body
                         element.body[element.body.length-1] = new_element
                         element.next_key = null # No more next_key
                 else if element.type is 'array'
+                    body = @extract_data_from_query
+                        size_stack: size_stack
+                        query: query.slice entry_start
+                        context: element.context
+                        position: position+entry_start
+                    if body is null
+                        return null
                     new_element =
                         type: 'array_entry'
                         complete: false
-                        body: @extract_data_from_query
-                            query: query.slice entry_start
-                            context: element.context
-                            position: position+entry_start
+                        body: body
                     if new_element.body.length > 0
                         element.body.push new_element
+                        size_stack++
+                        if size_stack > @max_size_stack
+                            return null
                 stack.push element
+                size_stack++
+                if size_stack > @max_size_stack
+                    return null
             else if start isnt i
                 if query.slice(start) of element.context
                     element.name = query.slice start
@@ -1118,6 +1220,9 @@
                     element.position = position+start
                     element.complete = false
                 stack.push element
+                size_stack++
+                if size_stack > @max_size_stack
+                    return null
             return stack

         # Decide if we have to show a suggestion or a description


Fix for review

Fix for review

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -3441,10 +3441,10 @@
                     if next.length is 1
                         next = "0" + next
                     snippet += next
-                    if i > 4
+                    if i > 4 and i isnt size-1
                         snippet += "..."
                         break
-                    else
+                    else if i isnt str.length-1
                         snippet += " "
                 return "<binary, #{sizeStr}, \"#{snippet}\">"


make margin-top adjustments optional

make margin-top adjustments optional

--- src/ioiojs/arrowScroller.coffee
+++ src/ioiojs/arrowScroller.coffee
@@ -15,6 +15,7 @@
         right: 'right'
       arrow:
         width:36
+      marginTop: true
     ,options

     # create a fake debug if the debug does not exist.
@@ -70,14 +71,17 @@
         .css('width',settings.arrow.width)
         .css('height',height)
         .css('margin-left',width + margin)
-        .css('margin-top',-height)
         .css('cursor','pointer')
         .click(doScrollLeft)
-
+
       element
         .css('margin-left',margin)
-        .css('margin-top',-height)
         .before(arrowLeft)
         .after(arrowRight)

+      if (true is setttings.marginTop)
+        arrowRight.css('margin-top',-height)
+        element.css('margin-top',-height)
+
+
 )(jQuery)


Further specify where we put alerts so they don't show up in testing.

--HG--
branch : qunit

--- src/js/collection-editor.coffee
+++ src/js/collection-editor.coffee
@@ -227,7 +227,7 @@
       crossDomain: true
       error: (jqXHR, textStatus, errorThrown) ->
         console.log "AJAX Error: #{textStatus}"
-        $('h1').after $('<div>').attr('class','alert alert-error').attr('id','collection_access_error').append('You do not have permission to access this collection.')
+        $('.container > h1').after $('<div>').attr('class','alert alert-error').attr('id','collection_access_error').append('You do not have permission to access this collection.')
         disable_collection_form()
       success: (data) ->
         console.log data
@@ -266,7 +266,7 @@
       crossDomain: true
       error: (jqXHR, textStatus, errorThrown) ->
         console.log "AJAX Error: #{textStatus}"
-        # $('h1').after $('<div>').attr('class','alert alert-warning').append('Error retrieving profile info.')
+        # $('.container > h1').after $('<div>').attr('class','alert alert-warning').append('Error retrieving profile info.')
       success: (data) ->
         set_cookie('author_name',data['name'],3600)
         $('#Author').attr('value',data['name'])
@@ -374,7 +374,7 @@
     dataType: 'xml'
     error: (jqXHR, textStatus, errorThrown) ->
       console.log "AJAX Error: #{textStatus}"
-      $('h1').after $('<div>').attr('class','alert alert-error').append("Error loading the collection capabilities URL \"#{cite_collection_editor_config['capabilities_url']}\".")
+      $('.container > h1').after $('<div>').attr('class','alert alert-error').append("Error loading the collection capabilities URL \"#{cite_collection_editor_config['capabilities_url']}\".")
     success: (data) ->
       collections = $(data).find('citeCollection')
       select = $('<select>')
@@ -402,7 +402,7 @@
         build_collection_form selected_collection
         load_collection_form()
         unless get_cookie 'access_token'
-          $('h1').after $('<div>').attr('class','alert alert-warning').attr('id','oauth_access_warning').append('You have not authorized this application to access your Google Fusion Tables. ')
+          $('.container > h1').after $('<div>').attr('class','alert alert-warning').attr('id','oauth_access_warning').append('You have not authorized this application to access your Google Fusion Tables. ')
           $('#oauth_access_warning').append $('<a>').attr('href',google_oauth_url()).append('Click here to authorize.')
           disable_collection_form()
       $('#collection_select').change()


fix format of post_query response

fix format of post_query response

--- src/smc-util/client.coffee
+++ src/smc-util/client.coffee
@@ -1848,7 +1848,7 @@
             if resp.error
                 opts.cb(resp.error)
             else
-                opts.cb(undefined, resp.result)
+                opts.cb(undefined, {query:resp.result})
         return

     query: (opts) =>


This is junk

This is junk

--- oncue-service/app/assets/javascripts/app_OLD.coffee
+++ oncue-service/app/assets/javascripts/app_OLD.coffee
@@ -1,85 +0,0 @@
-
-# ----------------------
-# ---- Application -----
-# ----------------------
-
-App = new Backbone.Marionette.Application();
-
-#
-# Intialize the Web Socket
-#
-App.addInitializer( ->
-  websocket = new WebSocket("ws://#{window.location.hostname}:9000/websocket")
-#  websocket.onopen = -> console.log "Web socket connected."
-#  websocket.onerror = ->
-#    console.log "Failed to create web socket."
-#    App.vent.trigger("websocket:failed")
-#  websocket.onmessage = (message) ->
-#    messageData = JSON.parse(message.data)
-#    eventKey = _.keys(messageData)[0]
-#    subject = _.first(eventKey.split(':'))
-#    event = _.last(eventKey.split(':'))
-#    payload = messageData[eventKey][subject]
-#    App.vent.trigger("#{subject}:#{event}", payload)
-##    console.log "Subject: #{subject} , Event: #{event}, Payload: #{payload}"
-)
-
-App.vent.on("websocket:failed", ->
-  console.log "Web socket failed :-("
-)
-
-# ------------------
-# --- Web Socket ---
-# ------------------
-
-App.module("WebSocket", (WebSocket, App, Backbone, Marionette, $, _) ->
-
-  WebSocket.addInitializer( ->
-#    websocket = new WebSocket("ws://#{window.location.hostname}:9000/websocket")
-#    websocket.onopen = -> console.log "Web socket connected."
-#    websocket.onerror = ->
-#      console.log "Failed to create web socket."
-#      App.vent.trigger("websocket:failed")
-#    websocket.onmessage = (message) ->
-#      messageData = JSON.parse(message.data)
-#      eventKey = _.keys(messageData)[0]
-#      subject = _.first(eventKey.split(':'))
-#      event = _.last(eventKey.split(':'))
-#      payload = messageData[eventKey][subject]
-#      App.vent.trigger("#{subject}:#{event}", payload)
-    console.log "WTF wheres me sockit?"
-  )
-)
-
-# ----------------
-# --- Entities ---
-# ----------------
-
-App.module("Entities", (Entities, App, Backbone, Marionette, $, _) ->
-
-  class Entities.Agent extends Backbone.Model
-
-  class Entities.AgentCollection extends Backbone.Collection
-    model: Entities.Agent
-
-  Entities.addInitializer( ->
-    agents = new Entities.AgentCollection()
-
-    App.vent.on("agent:started", (agentData) ->
-      console.log "Agent started!"
-      console.log agentData
-      agents.add(new Entities.Agent(agentData))
-    )
-  )
-)
-
-App.start()
-
-
-
-
-
-
-
-
-


remove unused jwt.coffee

remove unused jwt.coffee

--- src/jwt.coffee
+++ src/jwt.coffee
@@ -1,20 +0,0 @@
-jwt = require 'jwt-simple'
-atob = require 'atob'
-
-module.exports =
-
-  decode: (token, keyFn, noVerify) ->
-    key = switch typeof keyFn
-      when 'string'
-        keyFn
-      when 'function'
-        [ _, claimsA ] = token.split '.'
-        claims = try JSON.parse atob claimsA
-        console.log claims
-        keyFn.call this, claims
-      else throw new Error "key must be either a string or a function"
-    console.log key
-    jwt.decode token, key, noVerify
-
-  encode: (payload, key, algorithm) ->
-    jwt.encode payload, key, algorithm


color highlighting bug fixed

color highlighting bug fixed

--- app/views/paragraph.coffee
+++ app/views/paragraph.coffee
@@ -81,6 +81,10 @@
     groupedSelections = @groupSelectionsByPosition(@get('value.selections'), 'startPosition')
     @openTags(groupedSelections, options)

+    # close last tags
+    groupedEndingSelections = @groupSelectionsByPosition(options.startedSelections, 'endPosition')
+    @closeTags(groupedEndingSelections, options)
+
     # ending
     options.html += options.text.slice(options.prevPosition)

@@ -117,8 +121,7 @@
       options.startedSelections.removeObjects(selections)

       spanTag = ''
-      console.log('eee')
-      spanTag += @openingSpanTag(selections) if options.startedSelections.get('length')
+      spanTag += @openingSpanTag(options.startedSelections) if options.startedSelections.get('length')

       options.html += beforeText + spanTag
       options.prevPosition = position
@@ -127,14 +130,13 @@
     className = selections.mapBy('id').join(' ')

     color = selections.get('lastObject.label.color')
-
     color = color[0] if Ember.isArray(color) #fix
-
+
     if selections.get('length') > 1
       otherColor = selections.get('firstObject.label.color')
       otherColor = otherColor[0] if Ember.isArray(otherColor) #fix
       color = $.xcolor.combine color, otherColor
-      console.log color
+
     ""

   setContentOld: ->


:art:

:art:

--- lib/archive-editor.coffee
+++ lib/archive-editor.coffee
@@ -7,7 +7,7 @@

 isPathSupported = (filePath) ->
   switch path.extname(filePath)
-    when '.egg', '.epub', '.jar', '.love', '.tar', '.tgz', '.war', '.whl', '.xpi', '.zip', '.nupkg'
+    when '.egg', '.epub', '.jar', '.love', '.nupkg', '.tar', '.tgz', '.war', '.whl', '.xpi', '.zip'
       return true
     when '.gz'
       return path.extname(path.basename(filePath, '.gz')) is '.tar'


Call addOpener instead of registerOpener

--- lib/archive-editor.coffee
+++ lib/archive-editor.coffee
@@ -11,7 +11,7 @@
   atom.deserializers.add(this)

   @activate: ->
-    atom.workspace.registerOpener (filePath='') ->
+    atom.workspace.addOpener (filePath='') ->
       # Check that the file path exists before opening in case something like
       # an http: URI is being opened.
       if archive.isPathSupported(filePath) and fs.isFileSync(filePath)


Allows unknown keys

Allows unknown keys

--- api/apps/articles/model/retrieve.coffee
+++ api/apps/articles/model/retrieve.coffee
@@ -6,7 +6,7 @@
 moment = require 'moment'

 @toQuery = (input, callback) ->
-  Joi.validate input, schema.querySchema, (err, input) ->
+  Joi.validate input, schema.querySchema, { stripUnknown: true }, (err, input) ->
     return callback err if err
     # Separate "find" query from sort/offest/limit
     { limit, offset, sort } = input


Uncommented code sign

Uncommented code sign

--- build/Gruntfile.coffee
+++ build/Gruntfile.coffee
@@ -64,7 +64,7 @@
     'copy-resources',
     'install-spark-dev',
     'install-unpublished-packages',
-    # 'patch-code',
+    'patch-code',
     'build-app',
     'package-app'
   ]


kill the svg namespace, d3 uses createElementNS

--- src/packages/command-logger/src/command-logger-view.coffee
+++ src/packages/command-logger/src/command-logger-view.coffee
@@ -144,10 +144,10 @@
             .append('div')
             .style('width', "#{w}px")
             .style('height', "#{h}px")
-            .append('svg:svg')
+            .append('svg')
             .attr('width', w)
             .attr('height', h)
-            .append('svg:g')
+            .append('g')
             .attr('transform', 'translate(.5,.5)')

     nodes = treemap.nodes(root).filter((d) -> not d.children)
@@ -155,17 +155,17 @@
     cell = svg.selectAll('g')
               .data(nodes)
               .enter()
-              .append('svg:g')
+              .append('g')
               .attr('class', 'node')
               .attr('transform', (d) -> "translate(#{d.x},#{d.y})")
               .on('click', (d) -> if node is d.parent then zoom(root) else zoom(d.parent))

-    cell.append('svg:rect')
+    cell.append('rect')
         .attr('width', (d) -> d.dx - 1)
         .attr('height', (d) -> d.dy - 1)
         .style('fill', (d) -> color(d.parent.name))

-    cell.append('svg:foreignObject')
+    cell.append('foreignObject')
         .attr('width', (d) -> d.dx - 1)
         .attr('height', (d) -> d.dy - 1)
         .attr('class', 'foreign-object')


Tidied up code

Tidied up code

--- lib/class-complete.coffee
+++ lib/class-complete.coffee
@@ -22,7 +22,7 @@
         editor.selectToBeginningOfLine()

         buffer += "#{className} = (function() {\n"
-        buffer += "\tfunction " + className + "("
+        buffer += "\tfunction #{className}("

         if parameters
             for param in [0..parameters.length - 1]


Fix commit image js

Fix commit image js

Signed-off-by: Dmitriy Zaporozhets <be23d75b156792e5acab51b196a2deb155d35d6a@gmail.com>

--- app/assets/javascripts/commit.js.coffee
+++ app/assets/javascripts/commit.js.coffee
@@ -1,6 +1,6 @@
 class Commit
   constructor: ->
-    $('.files .file').each ->
+    $('.files .diff-file').each ->
       new CommitFile(this)

 @Commit = Commit


Bezier: extracting margins

Bezier: extracting margins

--- app/assets/javascripts/d3/bezier.coffee
+++ app/assets/javascripts/d3/bezier.coffee
@@ -8,7 +8,11 @@

     can_be_shown_as_table: -> true

-    outer_height: => @height + 60
+    margins:
+      top: 20
+      bottom: 20
+      left: 20
+      right: 30

     # This chart rendering is fairly complex. Here is the big picture:
     # this bezier chart is basically a stacked area chart. D3 provides some
@@ -21,13 +25,8 @@
     # required to draw the paths (and add some nice interpolations)
     #
     draw: =>
-      margins =
-        top: 20
-        bottom: 20
-        left: 20
-        right: 30
-
-      @width = @available_width() - (margins.left + margins.right)
+      @width = @available_width() - (@margins.left + @margins.right)
+
       # height of the series section
       @series_height = 190

@@ -36,15 +35,15 @@

       estimated_legend_height = legend_rows * 15

-      @height = @series_height + margins.top + margins.bottom + 20 + estimated_legend_height
+      @height = @series_height + @margins.top + @margins.bottom + 20 + estimated_legend_height

       @series_width = @width - 15
       @svg = d3.select(@container_selector())
         .append("svg:svg")
-        .attr("height", @height + margins.top + margins.bottom)
-        .attr("width", @width + margins.left + margins.right)
+        .attr("height", @height + @margins.top + @margins.bottom)
+        .attr("width", @width + @margins.left + @margins.right)
         .append("svg:g")
-        .attr("transform", "translate(#{margins.left}, #{margins.top})")
+        .attr("transform", "translate(#{@margins.left}, #{@margins.top})")

       @draw_legend
         svg: @svg