clean up loader code

clean up loader code

--- chrome/src/gmailr.coffee
+++ chrome/src/gmailr.coffee
@@ -79,6 +79,7 @@
     loaded: false
     inConversationView: false
     xhrWatcher: null
+    delayedLoader: null

     EVENT_VIEW_THREAD:        'viewThread'
     EVENT_ARCHIVE:            'archive'
@@ -116,26 +117,22 @@
         return

       dbg "Initializing Gmailr API"
-      count = 0
       # Here we do delayed loading until success. This is in the case
       # that our script loads after Gmail has already loaded.
       load = =>
         @elements.canvas = $("[style*='min-height: 100%;']")
         @elements.body = @elements.canvas.find(".nH").first()
         if @loaded
-          dbg "Delayed loader success." unless count is 0
+          clearInterval @delayedLoader
+          dbg "Delayed loader success."
           @elements.body.bind 'DOMSubtreeModified', @detectDOMEvents
-
-          clearInterval @delayed_loader
         else
-          dbg "Calling delayed loader..."
-          count++
-
+          dbg "Calling delayed loader..."
           # we search from the body node, since there's no event to attach to
           @bootstrap cb
         return

-      @delayed_loader = setInterval load, 500
+      @delayedLoader = setInterval load, 500
       return

     ###


make unread change event work again

make unread change event work again

--- chrome/src/gmailr.coffee
+++ chrome/src/gmailr.coffee
@@ -71,6 +71,7 @@

     debug:                false
     priorityInboxLink:    null
+    inboxLink:            null
     currentNumUnread:     null
     currentInboxCount:    null
     elements:             {}
@@ -126,9 +127,9 @@
         if @loaded
           clearInterval @delayedLoader
           dbg "Delayed loader success."
-          @elements.body.bind 'DOMSubtreeModified', @detectDOMEvents
+          @elements.body.children().on 'DOMSubtreeModified', @detectDOMEvents
         else
-          dbg "Calling delayed loader..."
+          dbg "Calling delayed loader..."
           # we search from the body node, since there's no event to attach to
           @bootstrap cb
         return
@@ -243,8 +244,7 @@

     getInboxLink: ->
       # use the inbox link as an anchor
-      v = $(@elements.body).find "a[href$='#inbox'][title^='Inbox']"
-      v.first() or null
+      @elements.body.find("a[href$='#inbox'][title^='Inbox']").first() or null

     liveLeftMenuItem: ->
       return null  unless @loaded
@@ -411,15 +411,18 @@
       return

     detectDOMEvents: (e) =>
+      # don't bubble
+      e.stopPropagation()
+
       # dbg "DOM changed", e.target
       for ignored in @ignoreDOMElements
         # check all the ignored DOM elements
         if $.contains ignored, e.target
           # the target from where the change came is a descendant of an ignored element
           # dbg "...but the event came from a descendant of an ignored element"
-          return
+          return false

-      el = $(e.target)
+      el = $ e.target

       # Left Menu Changes
       #var s = this.liveLeftMenuItem();
@@ -427,19 +430,13 @@
       #                this.currentLeftMenuItem = s;
       #                this.notify('tabChange', s);
       #            }
-      #
-
-      #
-      #            // Unread change
-      #            var l = this.getInboxLink() || this.priorityInboxLink;
-      #            if((el[0] == l[0]) || isDescendant(el, l)) {
-      #                var newCount = this.numUnread();
-      #                if(this.currentNumUnread != newCount) {
-      #                    this.notify(@EVENT_UNREAD_CHANGE, newCount, this.currentNumUnread);
-      #                    this.currentNumUnread = newCount;
-      #                }
-      #            }
-      #
+
+      newCount = @numUnread()
+      if @currentNumUnread isnt newCount
+          dbg "Unread count changed"
+          @notify @EVENT_UNREAD_CHANGE, newCount, @currentNumUnread
+          @currentNumUnread = newCount
+
       if @elements.canvas.find(".ha").length > 0
         unless @inConversationView
           @inConversationView = true
@@ -457,7 +454,6 @@
             @notify @EVENT_INBOX_COUNT_CHANGE, toolbarCount, @currentInboxCount  if @currentInboxCount isnt null
             @currentInboxCount = toolbarCount

-
   Gmailr = new Gmailr

   window.Gmailr = Gmailr


Fix some error wording

Fix some error wording

--- server/middleware/api.coffee
+++ server/middleware/api.coffee
@@ -97,7 +97,7 @@
   { endDate, ends } = req.body
   ends ?= endDate
   unless ends and DATETIME_REGEX.test(ends)
-    throw new errors.UnprocessableEntity('ends is not a properly formatted.')
+    throw new errors.UnprocessableEntity('ends is not properly formatted.')

   { free } = user.get('stripe') ? {}
   if free is true
@@ -134,7 +134,7 @@

   { ends } = req.body
   unless ends and DATETIME_REGEX.test(ends)
-    throw new errors.UnprocessableEntity('ends is not a properly formatted.')
+    throw new errors.UnprocessableEntity('ends is not properly formatted.')

   now = new Date().toISOString()
   if ends < now


Decrease MAX_CHECKIN_PER_SESSION

Decrease MAX_CHECKIN_PER_SESSION

--- server/models/Checkin.coffee
+++ server/models/Checkin.coffee
@@ -37,4 +37,4 @@

 export default Checkin

-export MAX_CHECKIN_PER_SESSION = [15, 15]
+export MAX_CHECKIN_PER_SESSION = [10, 10]


:runner::clock1130: Updated in browser at strd6.github.io/editor

--- pixie.cson
+++ pixie.cson
@@ -1,4 +1,4 @@
-version: "0.2.3-pre.0"
+version: "0.2.3-pre.1"
 entryPoint: "main"
 remoteDependencies: [
   "https://code.jquery.com/jquery-1.10.1.min.js"


Disallow single-line `IF expr ELSE` without `THEN`

* Supplement missing block before `ELSE` token only for multi-line `if`.
* Don't prematurely remove `TERMINATOR` before `ELSE` (so we can  differentiate single- and multi-line forms).
* Cleanup: Remove `WHEN` from `EXPRESSION_CLOSE`. (Only `LEADING_WHEN` tokens are preceded by a `TERMINATOR`.)
* Cleanup: Remove really old, inapplicable text from comment.

--- rewriter.coffee
+++ rewriter.coffee
@@ -355,8 +355,7 @@

   # Because our grammar is LALR(1), it can't handle some single-line
   # expressions that lack ending delimiters. The **Rewriter** adds the implicit
-  # blocks, so it doesn't need to. ')' can close a single-line block,
-  # but we need to make sure it's balanced.
+  # blocks, so it doesn't need to.
   addImplicitIndentation: ->
     starter = indent = outdent = null

@@ -370,12 +369,13 @@

     @scanTokens (token, i, tokens) ->
       [tag] = token
-      if tag is 'TERMINATOR' and @tag(i + 1) is 'THEN'
-        tokens.splice i, 1
-        return 0
-      if tag is 'ELSE' and @tag(i - 1) isnt 'OUTDENT'
-        tokens.splice i, 0, @indentation()...
-        return 2
+      if tag is 'TERMINATOR'
+        if @tag(i + 1) is 'ELSE' and @tag(i - 1) isnt 'OUTDENT'
+          tokens.splice i, 1, @indentation()...
+          return 1
+        if @tag(i + 1) in ['THEN', 'ELSE']
+          tokens.splice i, 1
+          return 0
       if tag is 'CATCH'
         for j in [1..2] when @tag(i + j) in ['OUTDENT', 'TERMINATOR', 'FINALLY']
           tokens.splice i + j, 0, @indentation()...
@@ -452,7 +452,7 @@
   EXPRESSION_END  .push INVERSES[left] = rite

 # Tokens that indicate the close of a clause of an expression.
-EXPRESSION_CLOSE = ['CATCH', 'WHEN', 'ELSE', 'FINALLY'].concat EXPRESSION_END
+EXPRESSION_CLOSE = ['CATCH', 'FINALLY'].concat EXPRESSION_END

 # Tokens that, if followed by an `IMPLICIT_CALL`, indicate a function invocation.
 IMPLICIT_FUNC    = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS']


Make getTotalLength() return a value in seconds

--- mp4.coffee
+++ mp4.coffee
@@ -809,11 +809,28 @@
         sampleDelta: sampleDelta
     return

+  # Returns the total length of this media in seconds
   getTotalLength: ->
+    # mdia
+    #   mdhd <- find target
+    #   hdlr
+    #   minf
+    #     vmhd
+    #     dinf
+    #       dref
+    #         url
+    #     stbl
+    #       stsd
+    #         avc1
+    #           avcC
+    #           btrt
+    #       stts <- self
+    mdhdBox = @findParent('mdia').find('mdhd')
+
     time = 0
     for entry in @entries
       time += entry.sampleDelta * entry.sampleCount
-    return time
+    return time / mdhdBox.timescale

   # Returns a decoding time for the given sample number
   getDecodingTime: (sampleNumber) ->


fixed issue with enabling tuples

fixed issue with enabling tuples

--- server/security/queries.coffee
+++ server/security/queries.coffee
@@ -138,6 +138,14 @@
       sipSet: Match.Optional(Match.OneOf(null, Match.App.IPSetId))
       anySetEnabled: Match.Optional(Boolean)
       anySet: Match.Optional(Match.OneOf(null, Match.App.IPSetId))
+      tupleFileEnabled: Match.Optional(Boolean)
+      tupleFile: Match.Optional(Match.OneOf(null, Match.App.TupleId))
+      tupleDirectionEnabled: Match.Optional(Boolean)
+      tupleDirection: Match.Optional(String)
+      tupleDelimiterEnabled: Match.Optional(Boolean)
+      tupleDelimiter: Match.Optional(String)
+      tupleFieldsEnabled: Match.Optional(Boolean)
+      tupleFields: Match.Optional(String)
       dportEnabled: Match.Optional(Boolean)
       dport: Match.Optional(String)
       sportEnabled: Match.Optional(Boolean)


Change staging api endpoint

Change staging api endpoint

--- config/staging.coffee
+++ config/staging.coffee
@@ -1,3 +1,3 @@
 module.exports = {
-  "apiEndpoint": "https://cobudget-api.herokuapp.com"
+  "apiEndpoint": "https://staging-cobudget-api.herokuapp.com"
 }


moving profiling stuff into a function

moving profiling stuff into a function

--- console/runner.coffee
+++ console/runner.coffee
@@ -20,6 +20,51 @@
   for p in classpath
     data = exports.read_binary_file "#{p}/#{cls}.class"
     return new ClassFile data if data?
+
+run_profiled = (rs, cname, java_cmd_args, hot=false) ->
+  if hot
+    jvm.run_class rs, cname, java_cmd_args
+  timings = {}
+  call_counts = {}
+  profiled_fn = (old_fn) -> ->
+    method = rs.curr_frame().method
+    caller = rs.meta_stack().get_caller(1).method
+    fn_sig = (fn) -> "#{fn.class_type.toClassString()}::#{fn.name}"
+    method_name = fn_sig method
+    hash = "#{if caller? then fn_sig caller else "program"}|#{method_name}"
+    timings[hash] ?= 0
+    call_counts[method_name] ?= 0
+
+    start = (new Date).getTime()
+    old_fn.call this, arguments...
+    end = (new Date).getTime()
+
+    timings[hash] += end - start
+    call_counts[method_name]++
+
+  methods.Method::run_bytecode = profiled_fn(methods.Method::run_bytecode)
+  methods.Method::run_manually = profiled_fn(methods.Method::run_manually)
+
+  jvm.run_class rs, cname, java_cmd_args
+
+  self_timings = {}
+  total_timings = {}
+  for k, v of timings
+    [caller,method] = k.split "|"
+    self_timings[method]  ?= 0
+    self_timings[caller]  ?= 0
+    total_timings[method] ?= 0
+    self_timings[method]  += v
+    self_timings[caller]  -= v
+    total_timings[method] += v
+  arr = (name: k, total: total_timings[k], self: v, counts:call_counts[k] for k, v of self_timings)
+  arr.sort (a, b) -> b.self - a.self
+  console.log "\nProfiler results: #{total_timings["#{cname}::main"]} ms total"
+  console.log ['total','self','calls','self ms/call','name'].join '\t'
+  for entry in arr[0..30]
+    avg = entry.self / entry.counts
+    console.log "#{entry.total}\t#{entry.self}\t#{entry.counts}\t#{avg.toFixed 1}\t#{entry.name}"
+

 if require.main == module
   optimist = require 'optimist'
@@ -61,47 +106,7 @@
   rs = new runtime.RuntimeState(stdout, read_stdin, exports.read_classfile)
   java_cmd_args = (argv.java?.toString().split /\s+/) or []

-  if argv.profile
-    if argv.profile is 'hot'
-      jvm.run_class rs, cname, java_cmd_args
-    timings = {}
-    call_counts = {}
-    profiled_fn = (old_fn) -> ->
-      method = rs.curr_frame().method
-      caller = rs.meta_stack().get_caller(1).method
-      fn_sig = (fn) -> "#{fn.class_type.toClassString()}::#{fn.name}"
-      method_name = fn_sig method
-      hash = "#{if caller? then fn_sig caller else "program"}|#{method_name}"
-      timings[hash] ?= 0
-      call_counts[method_name] ?= 0
-
-      start = (new Date).getTime()
-      old_fn.call this, arguments...
-      end = (new Date).getTime()
-
-      timings[hash] += end - start
-      call_counts[method_name]++
-
-    methods.Method::run_bytecode = profiled_fn(methods.Method::run_bytecode)
-    methods.Method::run_manually = profiled_fn(methods.Method::run_manually)
-
-  jvm.run_class rs, cname, java_cmd_args, null, argv.compile
-
-  if argv.profile
-    self_timings = {}
-    total_timings = {}
-    for k, v of timings
-      [caller,method] = k.split "|"
-      self_timings[method]  ?= 0
-      self_timings[caller]  ?= 0
-      total_timings[method] ?= 0
-      self_timings[method]  += v
-      self_timings[caller]  -= v
-      total_timings[method] += v
-    arr = (name: k, total: total_timings[k], self: v, counts:call_counts[k] for k, v of self_timings)
-    arr.sort (a, b) -> b.self - a.self
-    console.log "\nProfiler results: #{total_timings["#{cname}::main"]} ms total"
-    console.log ['total','self','calls','self ms/call','name'].join '\t'
-    for entry in arr[0..30]
-      avg = entry.self / entry.counts
-      console.log "#{entry.total}\t#{entry.self}\t#{entry.counts}\t#{avg.toFixed 1}\t#{entry.name}"
+  if argv.profile?
+    run_profiled rs, cname, java_cmd_args, argv.hot
+  else
+    jvm.run_class rs, cname, java_cmd_args, null, argv.compile


cleanup imports

cleanup imports

--- source/domain/aggregate.coffee
+++ source/domain/aggregate.coffee
@@ -1,6 +1,5 @@

-Event = Space.messaging.Event
-Command = Space.messaging.Command
+{Event, Command} = Space.messaging

 class Space.eventSourcing.Aggregate extends Space.Object


:sweet_potato::disappointed: Updated in browser at strd6.github.io/editor

--- pixie.cson
+++ pixie.cson
@@ -1,4 +1,4 @@
-version: "0.1.6"
+version: "0.1.7"
 entryPoint: "main"
 remoteDependencies: [
   "https://code.jquery.com/jquery-1.10.1.min.js"


:pizza::crying_cat_face: Updated at https://danielx.net/editor/

--- pixie.cson
+++ pixie.cson
@@ -4,6 +4,9 @@
 beautiful songs right in your browser and share them with the world!
 """
 version: "0.1.0"
+publish:
+  s3:
+    basePath: "public/danielx.net"
 remoteDependencies: [
   "https://code.jquery.com/jquery-1.11.0.min.js"
 ]


Number of feeds now updates. fixes #36

Number of feeds now updates. fixes #36

--- src/frontend/network.coffee
+++ src/frontend/network.coffee
@@ -58,6 +58,9 @@
                 )
             baliseName          = '#' + baliseVideoId
             $(baliseName).attr 'src', window.URL.createObjectURL(event.stream)
+            feed = $("#nbFeed").text()
+            feed = Number(feed) + 1
+            $("#nbFeed").text(feed)
             do @autoZoomWebcam
         options.removestream        = (event) =>
             return
@@ -189,13 +192,11 @@
             when 'peer.remove'
                 cinfo     = cinfos
                 peerInfos = @connections[cinfo.cid]
-
                 event = EventManager.getEvent "updateUserList"
                 if event?
                     event peerInfos, "remove"

                 peerInfos.peerConnection.close()
-
                 baliseBlockId = "#block" + cinfo.cid
                 $(baliseBlockId).remove()
                 mainCamId = $('#mainCam video').attr 'id'
@@ -203,6 +204,11 @@
                     $("#mainCam video").remove()
                     do @autoZoomWebcam

+                feed = $("#nbFeed").text()
+                feed = Number(feed) - 1
+                if feed < 0
+                    feed = 0
+                $("#nbFeed").text(feed)
                 delete @connections[cinfo.cid]

     onMessageExchangeOffer      : (eventName, cinfos, sdp) =>


small changes

small changes

--- src/game/states/game.coffee
+++ src/game/states/game.coffee
@@ -27,7 +27,7 @@
     ]
   },
   {
-    name: 'Bootes the Herdsman',
+    name: 'Boötes, the Herdsman',
     numStars: 8,
     startingStar: 0,
     starsArray: [
@@ -115,7 +115,7 @@
     @constellation = []
     @levelcomplete = false
     @drawConstellation(@level)
-    @add.text(560, 595, @level.name, { font: "18px Arial", fill: "#FFFF00", align: "center" })
+    @add.text(550, 595, @level.name, { font: "18px Arial", fill: "#FFFF00", align: "center" })
     @player = @add.sprite x, y, 'player'
     @player.scale.set 0.5,0.5


Don’t need the workspaceElement

Don’t need the workspaceElement

--- spec/results-model-spec.coffee
+++ spec/results-model-spec.coffee
@@ -6,7 +6,7 @@
 waitsForPromise = (fn) -> window.waitsForPromise timeout: 30000, fn

 describe 'ResultsModel', ->
-  [workspaceElement, editor, searchPromise, resultsModel, searchPromise] = []
+  [editor, searchPromise, resultsModel, searchPromise] = []

   beforeEach ->
     atom.project.setPaths([path.join(__dirname, 'fixtures')])


fixed an expectation error caused by new coffee-script version

--- specs/otf_compiler_spec.coffee
+++ specs/otf_compiler_spec.coffee
@@ -41,7 +41,14 @@

   it 'should compile coffee files correctly', ->
     request.get 'http://localhost:1338/generated/coffee/1.js', (err, res) ->
-      expected = "\n  console.log('1');\n"
+      expected = '''
+      (function() {
+
+        console.log('1');
+
+      }).call(this);
+
+      '''
       expect(res.body).toEqual(expected)
       jasmine.asyncSpecDone()
     jasmine.asyncSpecWait()


split _getJob() into smaller functions to get jobs by different parameters, added support to search the root folder for a job if a nested folder had the same job

--- src/jenkins-enhanced.coffee
+++ src/jenkins-enhanced.coffee
@@ -485,87 +485,96 @@

     "HEALTH: #{result}\n"

-  _getJob: (escape = false) =>
-    # check if its a job we already stored
+  _getJob: =>
+    job = null
     if (typeof(@msg.match[1]) == "object")
-      return @msg.match[1]
-    # check if the user gave us a folder path to follow and a job
+	  # check if its a job we already stored
+      job = @msg.match[1]
     else
-      # otherwise search for it with what the user gave us
-      # if they gave us a folder use that
+      # check if the user gave us a folder path to follow and a job
       if (@msg.match[1].indexOf("/") != -1)
         folderPath = @msg.match[1].split("/")
         jobName = folderPath[folderPath.length-1]
         folderPath.splice(folderPath.length-1, 1)
+
         if (folderPath.length == 1)
-          folders = []
-          for server in @_serverManager.listServers()
-            folders = folders.concat(server.getFolderByName(folderPath[0]))
-          if (folders.length > 1)
-            @send "There are multiple folders with the name #{folderPath[0]}.  Please use `jenkins list` and an ID instead."
-          else if (folders.length == 1)
-            jobs = folders[0].getJobByName(jobName)
-            if (jobs.length > 1)
-              @send "There are multiple jobs with the name #{jobName} in #{folderPath[0]}.  Please use `jenkins list` and an ID instead."
-            else if (jobs.length == 1)
-              return jobs[0]
-            else
-              @send "There are no jobs with the name #{jobName} in #{folderPath[0]}."
-          else
-            @send "There are no folders with the name #{folderPath[0]}."
-
+          # when we only receive the folder name
+          job = @_getJobByFolderName(folderPath[0], jobName)
+          if (!job)
+            @send "There are no folders with the name #{folderPath[0]} that have a job called #{jobName}."
         else
-          # this is for an absolute path to the folder
-          folders = []
-          for server in @_serverManager.listServers()
-            curFolder = server.getFolder()
-            for folderName in folderPath
-              # this should either be of length 1 or length 0, as there cannot be two subfolders with the same name (in iterative mode)
-              nextFolder = curFolder.getFolderByName(folderName, false)
-              if (nextFolder.length == 1)
-                curFolder = nextFolder[0]
-              else
-                curFolder = null
-                break
-            # check if we found a folder
-            if (curFolder)
-              folders.push(curFolder)
-          # now make sure this path is valid for only one folder
-          if (folders.length > 1)
-            @send "There are multiple folders with the path #{folderPath.join('/')}.  Please use `jenkins list` and an ID instead."
-          else if (folders.length == 1)
-            jobs = folders[0].getJobByName(jobName)
-            if (jobs.length > 1)
-              @send "There are multiple jobs with the name #{jobName} in #{folderPath.join('/')}.  Please use `jenkins list` and an ID instead."
-            else if (jobs.length == 1)
-              return jobs[0]
-            else
-              @send "There are no jobs with the name #{jobName} in #{folderPath.join('/')}."
-          else
-            @send "There are no folders with the path #{folderPath.join('/')}."
+          # when we receive the absolute path to the job
+          job = @_getJobByAbsolutePath(folderPath, jobName)
+          if (!job)
+            @send "There are no folders with the path #{folderPath.join('/')} that have a job called #{jobName}."
       else
-        # this is when they just give us a job name
-        jobName = @msg.match[1].trim()
-
-        # if the provided name is an alias, provide it's mapped job name
-        aliases = @_getSavedAliases()
-        jobName     = aliases[jobName] if aliases[jobName]
-
-        jobName = if escape then @_querystring.escape(jobName) else jobName
+        # when we receive no folder information at all and only the job name
+        job = @_getJobByName(@msg.match[1].trim())
+    job
+
+  _getJobByFolders: (folders, jobName) =>
+    # find all jobs that are in the folders given, this is based off the presumption that all of the folders have the same name and some may/may not have the job we're searching for
+    jobs = []
+    for folder in folders
+      # only search for jobs in the current folder's directory, otherwise we risk duplicates
+      jobs = jobs.concat(folder.getJobByName(jobName, false))
+    if (jobs.length > 1)
+      # we're safe to just use the first one, as they should all have the same name
+      @send "There are multiple folders with the name #{folders[0].name} that have a job called #{jobName}.  Please use `jenkins list` and an ID instead."
+    else if (jobs.length == 1)
+      return jobs[0]
+    # no else case because there aren't any folders to pull a name from	to send a message to the user
+    null
+
+  _getJobByFolderName: (folderName, jobName) =>
+    # find all of the folders that have this name
+    folders = []
+    for server in @_serverManager.listServers()
+      if (folderName == "")
+        # if the folder name is empty, presume they're referencing the root folder
+        folders.push(server.getFolder())
+      else
+        folders = folders.concat(server.getFolderByName(folderName))
+    # find all possible jobs that match in all given folders
+    @_getJobByFolders(folders, jobName)
+
+  _getJobByAbsolutePath: (folderPath, jobName) =>
+    # find all folders that have this path
+    folders = []
+    for server in @_serverManager.listServers()
+      curFolder = server.getFolder()
+      for folderName in folderPath
+        # this should either be of length 1 or length 0, as there cannot be two subfolders with the same name (in iterative mode)
+        nextFolder = curFolder.getFolderByName(folderName, false)
+        if (nextFolder.length == 1)
+          curFolder = nextFolder[0]
+        else
+          curFolder = null
+          break
+      # check if we found a folder
+      if (curFolder)
+        folders.push(curFolder)
+    # now find all possible jobs
+    @_getJobByFolders(folders, jobName)
+
+  _getJobByName: (jobName) =>
+    # if the provided name is an alias, provide it's mapped job name
+    aliases = @_getSavedAliases()
+    jobName = aliases[jobName] if aliases[jobName]

-        jobs = []
-        # perform lookup
-        for server in @_serverManager.listServers()
-          job = server.getJobByName(jobName)
-          if job.length > 0
-            jobs = jobs.concat(job)
-        if jobs.length > 1
-          @send "There are multiple jobs with that name, please use an id from `jenkins list` instead."
-        else if jobs.length == 1
-          return jobs[0]
-        else
-          @send "There are no jobs with the name #{jobName}"
-    return null
+    jobs = []
+    # perform lookup
+    for server in @_serverManager.listServers()
+      job = server.getJobByName(jobName)
+      if job.length > 0
+        jobs = jobs.concat(job)
+    if jobs.length > 1
+      @send "There are multiple jobs with that name, please use an id from `jenkins list` instead or a folder path."
+    else if jobs.length == 1
+      return jobs[0]
+    else
+      @send "There are no jobs with the name #{jobName}"
+    null

   # Switch the index with the job name
   _getJobById: =>
@@ -603,7 +612,7 @@
   # --------
   _handleNewFolder: (err, res, body, server, folder) =>
     if err
-      @send err
+      @send "It appears an error occurred while contacting your Jenkins instance.  The error I received was #{err.code} from #{server.url}.  Please verify that your Jenkins instance is configured properly."
       return

     try
@@ -614,7 +623,7 @@

   _handleBuild: (err, res, body, server, folder) =>
     if err
-      @reply err
+      @reply "It appears an error occurred while contacting your Jenkins instance.  The error I received was #{err.code} from #{server.url}.  Please verify that your Jenkins instance is configured properly."
     else if 200 <= res.statusCode < 400 # Or, not an error code.
       job = @_getJob(false)
       @reply "(#{res.statusCode}) Build started for #{job.name} #{server.url}/#{job.path}"
@@ -625,7 +634,7 @@

   _handleDescribe: (err, res, body, server, folder) =>
     if err
-      @send err
+      @send "It appears an error occurred while contacting your Jenkins instance.  The error I received was #{err.code} from #{server.url}.  Please verify that your Jenkins instance is configured properly."
       return

     try
@@ -639,7 +648,7 @@

   _handleLast: (err, res, body, server, folder) =>
     if err
-      @send err
+      @send "It appears an error occurred while contacting your Jenkins instance.  The error I received was #{err.code} from #{server.url}.  Please verify that your Jenkins instance is configured properly."
       return

     try
@@ -655,7 +664,7 @@

   _handleLastBuildStatus: (err, res, body, server, folder) =>
     if err
-      @send err
+      @send "It appears an error occurred while contacting your Jenkins instance.  The error I received was #{err.code} from #{server.url}.  Please verify that your Jenkins instance is configured properly."
       return

     try
@@ -677,7 +686,7 @@

   _processListResult: (err, res, body, server, print = true) =>
     if err
-      @send err
+      @send "It appears an error occurred while contacting your Jenkins instance.  The error I received was #{err.code} from #{server.url}.  Please verify that your Jenkins instance is configured properly."
       return

     try


Significant rework of code; new features include:

- Pause button
- Next/Previous
- Updating date/map on drag

--- src/leaflet.timeline.coffee
+++ src/leaflet.timeline.coffee
@@ -8,88 +8,173 @@
 http://leafletjs.com
 ###

-L.TimelineVersion = '0.1.0'
+L.TimelineVersion = '0.2.0'

 L.Timeline = L.GeoJSON.extend
-  allData: []
-  allDates: []
-  initialize: (timedGeoJSON, @options) ->
-    L.GeoJSON.prototype.initialize.call this, undefined, @options
-    @_firstDate = Infinity
-    @_lastDate = -Infinity
-    if timedGeoJSON.features?
-      timedGeoJSON.features.forEach (feature) =>
-        startDate = ( new Date feature.properties.startDate ).getTime()
-        endDate = ( new Date feature.properties.endDate ).getTime()
-        @allData.push
-          geoJSON: feature
-          start: startDate
-          end: endDate
-        @allDates.push startDate
-        @allDates.push endDate
-        if startDate < @_firstDate then @_firstDate = startDate
-        if endDate > @_lastDate then @_lastDate = endDate
+  includes: L.Mixin.Events
+  times: []
+  ranges: []
+  options:
+    position: "bottomleft"
+    formatDate: (date) -> ""
+    enablePlayback: true
+    steps: 1000
+    duration: 10000
+    updateMapOnDrag: true
+    showTicks: true
+  initialize: (@timedGeoJSON, options) ->
+    L.GeoJSON.prototype.initialize.call this, undefined, options
+    L.extend @options, options
+    @process @timedGeoJSON
+
+  process: (data) ->
+    earliestStart = Infinity
+    latestEnd = -Infinity
+    data.features.forEach (feature) =>
+      start = ( new Date feature.properties.start ).getTime()
+      end = ( new Date feature.properties.end ).getTime()
+      @ranges.push
+        start: start
+        end: end
+        geoJSON: feature
+      @times.push start
+      @times.push end
+      if start < earliestStart then earliestStart = start
+      if end > latestEnd then latestEnd = end
+    @times = @times.sort()
+    if not @options.start then @options.start = earliestStart
+    if not @options.end then @options.end = latestEnd

   setTime: (time) ->
+    @fire 'timeline:change'
+    @time = (new Date time).getTime()
     @clearLayers()
-    @allData.forEach (range) =>
-      if range.start <= time and range.end >= time
+    @ranges.forEach (range) =>
+      if range.start <= @time and range.end >= @time
         @addData range.geoJSON
+
   onAdd: (map) ->
     L.GeoJSON.prototype.onAdd.call this, map
-    @dateSliderControl = L.Timeline.dateSliderControl this, @_firstDate, @_lastDate, @allDates
-    @dateSliderControl.addTo map
+    @timeSliderControl = L.Timeline.timeSliderControl this
+    @timeSliderControl.addTo map

-L.Timeline.DateSliderControl = L.Control.extend
-  options:
-    position: 'bottomleft'
-  initialize: (@timeline, @startDate, @endDate, @datelist) ->
-  buildDataList: (dates, container) ->
-    datalist = L.DomUtil.create 'datalist', '', container
-    datalistSelect = L.DomUtil.create 'select', '', datalist
-    dates.forEach (date) ->
+  getDisplayed: ->
+    showing = @ranges.filter (r) -> r.start <= @time and r.end >= @time
+    showing.map (shown) -> shown.geoJSON
+
+
+L.Timeline.TimeSliderControl = L.Control.extend
+  initialize: (@timeline) ->
+    @options.position = @timeline.options.position
+    @start = @timeline.options.start
+    @end = @timeline.options.end
+    @showTicks = @timeline.options.showTicks
+    @stepDuration = @timeline.options.duration / @timeline.options.steps
+    @stepSize = ( @end - @start ) / @timeline.options.steps
+
+  _buildDataList: (container, times) ->
+    @_datalist = L.DomUtil.create 'datalist', '', container
+    datalistSelect = L.DomUtil.create 'select', '', @_datalist
+    times.forEach (time) ->
       datalistOption = L.DomUtil.create 'option', '', datalistSelect
-      datalistOption.value = date
-    datalist
-  onAdd: (map) ->
-    @_container = L.DomUtil.create 'div',
+      datalistOption.value = time
+    @_datalist.id = "timeline-datalist-" + Math.floor( Math.random() * 1000000 )
+    @_timeSlider.setAttribute 'list', @_datalist.id
+
+  _makePlayPause: (container) ->
+    @_playButton = L.DomUtil.create 'button', 'play', container
+    @_playButton.addEventListener 'click', => @_play()
+    @_pauseButton = L.DomUtil.create 'button', 'pause', container
+    @_pauseButton.addEventListener 'click', => @_pause()
+
+  _makePrevNext: (container) ->
+    @_prevButton = L.DomUtil.create 'button', 'prev'
+    @_nextButton = L.DomUtil.create 'button', 'next'
+    @_playButton.parentNode.insertBefore @_prevButton, @_playButton
+    @_playButton.parentNode.insertBefore @_nextButton, @_pauseButton.nextSibling
+    @_prevButton.addEventListener 'click', @_prev.bind @
+    @_nextButton.addEventListener 'click', @_next.bind @
+
+  _makeSlider: (container) ->
+    @_timeSlider = L.DomUtil.create 'input', 'time-slider', container
+    @_timeSlider.type = "range"
+    @_timeSlider.min = @start
+    @_timeSlider.max = @end
+    @_timeSlider.value = @start
+    @_timeSlider.addEventListener 'mousedown', => @map.dragging.disable()
+    document.addEventListener     'mouseup',   => @map.dragging.enable()
+    @_timeSlider.addEventListener 'input', @_sliderChanged.bind @
+    @_timeSlider.addEventListener 'change', @_sliderChanged.bind @
+
+  _makeOutput: (container) ->
+    @_output = L.DomUtil.create 'output', 'time-text', container
+    @_output.innerHTML = @timeline.options.formatDate new Date @start
+
+  _nearestEventTime: (findTime, mode=0) ->
+    retNext = false
+    lastTime = @timeline.times[0]
+    for time in @timeline.times[1..]
+      if retNext then return time
+      if time >= findTime
+        if mode == -1
+          return lastTime
+        else if mode == 1
+          if time == findTime then retNext = true
+          else return time
+        else
+          prevDiff = Math.abs findTime - lastTime
+          nextDiff = Math.abs time - findTime
+          return if prevDiff < nextDiff then prevDiff else nextDiff
+      lastTime = time
+    lastTime
+
+  _prev: (e) ->
+    e.stopPropagation()
+    @_pause()
+    prevTime = @_nearestEventTime @timeline.time, -1
+    @_timeSlider.value = prevTime
+    @timeline.setTime prevTime
+
+  _pause: ->
+    clearTimeout @_timer
+    @container.classList.remove 'playing'
+
+  _play: ->
+    clearTimeout @_timer
+    if +@_timeSlider.value == @end then @_timeSlider.value = @start
+    @_timeSlider.stepUp @stepSize
+    @_sliderChanged { target: value: @_timeSlider.value }
+    unless +@_timeSlider.value == @end
+      @container.classList.add 'playing'
+      @_timer = setTimeout @_play.bind @, @stepDuration
+
+  _next: (e) ->
+    e.stopPropagation()
+    @_pause()
+    nextTime = @_nearestEventTime @timeline.time, 1
+    @_timeSlider.value = nextTime
+    @timeline.setTime nextTime
+
+  _sliderChanged: (e) ->
+    time = +e.target.value
+    @timeline.setTime time
+    @_output.innerHTML = @timeline.options.formatDate new Date time
+
+  onAdd: (@map) ->
+    container = L.DomUtil.create 'div',
                     'leaflet-control-layers ' +
                     'leaflet-control-layers-expanded ' +
                     'leaflet-timeline-controls'
-    @_playButton = L.DomUtil.create 'button', 'play-button', @_container
-    @_dateSlider = L.DomUtil.create 'input', 'date-slider', @_container
-    @_dateText = L.DomUtil.create 'output', 'date-text', @_container
-    @_dataList = @buildDataList @datelist, @_container
-    @_dataList.id = Math.floor(Math.random() * 999999999)
-    @_playButton.innerHTML = "▶"
-    @_dateSlider.type = "range"
-    @_dateSlider.min = @startDate
-    @_dateSlider.max = @endDate
-    @_dateSlider.value = @startDate
-    @_dateSlider.setAttribute 'list', @_dataList.id
-    @_dateSlider.addEventListener 'mousedown', -> map.dragging.disable()
-    sliderChanged = (e) =>
-      time = +e.target.value
-      @timeline.setTime time
-      @_dateText.innerHTML = @timeline.options.formatDate new Date time
-    @_dateSlider.addEventListener 'change', sliderChanged
-    document.addEventListener 'mouseup', -> map.dragging.enable()
-    @_dateText.innerHTML = @timeline.options.formatDate new Date @startDate
-    @timeline.setTime @startDate
-    @_playButton.addEventListener 'click', =>
-      stepDuration = 10
-      steps = 1000
-      stepSize = (@endDate - @startDate) / 1000
-      @_dateSlider.value = @startDate
-      step = 0
-      stepUp = =>
-        @_dateSlider.stepUp stepSize
-        sliderChanged {target:{value: @_dateSlider.value}}
-        if ++step < steps
-          setTimeout stepUp, stepDuration
-      stepUp()
-    @_container
+    buttonContainer = L.DomUtil.create 'div', 'button-container', container
+    @_makePlayPause buttonContainer
+    @_makeSlider container
+    if @showTicks
+      @_buildDataList container, @timeline.times
+      @_makePrevNext buttonContainer
+    @_makeOutput container
+    @timeline.setTime @start
+    @container = container

 L.timeline = (timedGeoJSON, options) -> new L.Timeline timedGeoJSON, options
-L.Timeline.dateSliderControl = (timeline, start, end, datelist) ->
-  new L.Timeline.DateSliderControl timeline, start, end, datelist
+L.Timeline.timeSliderControl = (timeline, start, end, timelist) ->
+  new L.Timeline.TimeSliderControl timeline, start, end, timelist


UI Copy: white label => white-label

UI Copy: white label => white-label

--- client/Intro/home.coffee
+++ client/Intro/home.coffee
@@ -86,7 +86,7 @@
       client    : '#home-page .browser'
       container : this
       wait      : 2200
-      message   : 'WHITE LABEL KODING'
+      message   : 'WHITE-LABEL KODING'
       offset    :
         top     : 25
         left    : 25
@@ -146,7 +146,7 @@

-
WHITE LABEL KODING

+
WHITE-LABEL KODING

               You can have your private Koding in the cloud, with your rules, your apps and your own members. Please contact us for further information.


[neo4j] added title to base decorator

[neo4j] added title to base decorator

--- server/lib/server/graph/decorators/base.coffee
+++ server/lib/server/graph/decorators/base.coffee
@@ -46,6 +46,7 @@
       bongo_            :
         constructorName : @datum.name
       slug              : @datum.slug
+      title             : @datum.title
       slug_             : @datum.slug_
       originId          : @datum.originId
       originType        : @datum.originType
@@ -81,8 +82,8 @@
   followerCount:->
     return @extractCountFromRelationData @datum, 'follower'

-  extractCountFromRelationData:(name)->
-    return @datum.relationData[name]?.length or 0
+  extractCountFromRelationData:(data, name)->
+    return data.relationData[name]?.length or 0

   decorateOverview:->
     overview =


Removed nonsensical lean model set calls that don't work in new Mongoose

--- server/queues/scoring/processTaskResult.coffee
+++ server/queues/scoring/processTaskResult.coffee
@@ -83,8 +83,6 @@
     callback null

 logTaskComputation = (callback) ->
-  @taskLog.set('calculationTimeMS', @clientResponseObject.calculationTimeMS)
-  @taskLog.set('sessions')  # Huh?
   @taskLog.calculationTimeMS = @clientResponseObject.calculationTimeMS
   @taskLog.sessions = @clientResponseObject.sessions
   @taskLog.save (err, saved) ->


improve synctex logging

improve synctex logging

--- services/clsi/app/coffee/CompileManager.coffee
+++ services/clsi/app/coffee/CompileManager.coffee
@@ -251,7 +251,7 @@
 		compileName = getCompileName(project_id, user_id)
 		CommandRunner.run compileName, command, directory, Settings.clsi.docker.image, timeout, {}, (error, output) ->
 			if error?
-				logger.err err:error, command:command, "error running synctex"
+				logger.err err:error, command:command, project_id:project_id, user_id:user_id, "error running synctex"
 				return callback(error)
 			callback(null, output.stdout)


Atom: adjust default fontsize

Atom: adjust default fontsize

--- opt/config.cson
+++ opt/config.cson
@@ -16,7 +16,6 @@
     preferredLineLength: 100
     tabLength: 2
     invisibles: {}
-    fontSize: 17
   "highlight-line": {}
   hashrocket:
     nodeExecutablePath: "/usr/local/bin/node"


Cursors now appear where we want them to! Fixes #4704961

--- math/expression_components_spec.coffee
+++ math/expression_components_spec.coffee
@@ -17,6 +17,9 @@
       @comp.isExpression()
       @comp.isFraction()
       @comp.isNumber()
+      @comp.isVariable()
+      @comp.isFraction()
+      @comp.isExponentiation()

 describe "Expression Components", ->
   beforeEach ->


Workspace: Load workspace if machine does exist

--- client/ide/routes.coffee
+++ client/ide/routes.coffee
@@ -216,6 +216,7 @@
     '/:name?/IDE/:machineLabel/:workspaceSlug': (routeInfo) ->

       {params} = routeInfo
+      {machineLabel} = params

       refreshWorkspaces ->

@@ -225,4 +226,8 @@

           if workspace
           then loadWorkspace params, workspace
-          else routeToLatestWorkspace {params}
+          else
+            for machine in KD.userMachines when machine.label is machineLabel
+              return loadWorkspace params
+
+            routeToLatestWorkspace {params}


Tune scroller

Tune scroller

--- source/assets/javascripts/app.js.coffee
+++ source/assets/javascripts/app.js.coffee
@@ -182,6 +182,10 @@
       scroller.attr { fill: '#000' }
     $(scroller.node).on 'mouseleave', ->
       scroller.attr { fill: '#b3b3b3' }
+    $(window).on 'scroll', ->
+      topBound = $('header.main').height()
+      if $(window).scrollTop() > topBound
+        scroller.remove()
   moveButton = (item, index) ->
     index = 0 if index > 11
     elementToMove = buttons.selectAll('g#'+item.id+' > g')[index]


Extract package_app out.

Extract package_app out.

--- build_detail.coffee
+++ build_detail.coffee
@@ -241,13 +241,44 @@
       log 'Compiling ended'
       resolve(yield Promise.all tasks)

-packageAppAsync = async (poi_version, app_dir, release_dir) ->
+packageAppAsync = async (poi_version, building_root, release_dir) ->
+  stage1_app = path.join building_root, 'stage1'
+  stage2_app = path.join building_root, 'app'
+  theme_root = path.join stage1_app, 'assets', 'themes'
+  release_path = path.join(release_dir, "app-#{poi_version}", "app.asar")
+
+  try
+    fs.removeSync stage1_app
+    fs.removeSync stage2_app
+  catch e
+  fs.ensureDirSync stage1_app
+  fs.ensureDirSync stage2_app
+
+  # Stage1: Everything downloaded and translated
+  download_themes = downloadThemesAsync theme_root
+  archive_app = execAsync "git archive HEAD | tar -x -C '#{stage1_app}'"
+  yield Promise.join download_themes,
+    (async ->
+      yield archive_app
+      yield fs.moveAsync path.join(stage1_app, 'default-config.cson'),
+        path.join(stage1_app, 'config.cson')
+      yield Promise.join(
+        translateCoffeeAsync(stage1_app),
+        (async ->
+          yield npmInstallAsync stage1_app, ['--production']
+          yield bowerInstallAsync stage1_app
+          )())
+      )()
+
+  # Stage2: Filtered copy
+  yield filterCopyAppAsync stage1_app, stage2_app
+
+  # Pack stage2 into app.asar
   log "Packaging app.asar."
-  release_path = path.join(release_dir, "app-#{poi_version}", "app.asar")
   try
     yield fs.removeAsync release_path
   catch e
-  yield packageAsarAsync app_dir, release_path
+  yield packageAsarAsync stage2_app, release_path
   release_path

 packageReleaseAsync = async (poi_fullname, electron_dir, release_dir) ->
@@ -346,6 +377,7 @@
   yield Promise.all (for plugin_dir in plugins_dir
     npmInstallAsync plugin_dir, ['--no-bin-links', '--no-progress', '--production'])

+  log "Now packing plugins into tarballs."
   yield runScriptAsync npm_exec_path, ['pack'].concat(plugins_dir),
     cwd: tarball_root

@@ -394,17 +426,6 @@
   building_root = path.join build_root, "building_#{poi_version}"
   release_dir = path.join build_root, release_dir_name

-  stage1_app = path.join building_root, 'stage1'
-  stage2_app = path.join building_root, 'app'
-
-  theme_root = path.join stage1_app, 'assets', 'themes'
-
-  try
-    fs.removeSync stage1_app
-    fs.removeSync stage2_app
-  catch e
-  fs.ensureDirSync stage1_app
-  fs.ensureDirSync stage2_app

   # Check npm version
   npm_version = (yield runScriptReturnStdoutAsync npm_exec_path, ['--version']).trim()
@@ -414,27 +435,7 @@
     log "Aborted."
     return

-  # Prepare stage1
-  download_themes = downloadThemesAsync theme_root
-  archive_app = execAsync "git archive HEAD | tar -x -C '#{stage1_app}'"
-  yield Promise.join download_themes,
-    (async ->
-      yield archive_app
-      yield fs.moveAsync path.join(stage1_app, 'default-config.cson'),
-        path.join(stage1_app, 'config.cson')
-      yield Promise.join(
-        translateCoffeeAsync(stage1_app),
-        (async ->
-          yield npmInstallAsync stage1_app, ['--production']
-          yield bowerInstallAsync stage1_app
-          )())
-      )()
-
-  # Prepare stage2
-  yield filterCopyAppAsync stage1_app, stage2_app
-
-  # Pack app.asar
-  app_path = yield packageAppAsync poi_version, stage2_app, release_dir
+  app_path = yield packageAppAsync poi_version, building_root, release_dir

   # Prepare stage 3
   stage3_info = yield Promise.all (


Bug fix for adding permission roles.

Bug fix for adding permission roles.

--- client/lib/access.coffee
+++ client/lib/access.coffee
@@ -348,7 +348,7 @@
     _parent: document
     personOrGroup: personOrGroup

-  changeRole data, if document.access is ACCESS.PRIVATE then ROLES.MAINTAINER else ROLES.READ_ACCESS
+  changeRole data, if document.access is ACCESS.PRIVATE then ROLES.READ_ACCESS else ROLES.MAINTAINER

 Template.rolesControlNoResults.events
   'click .add-and-invite': (e, template) ->


Première apparition des images de fond animée

--- js/game/entities/Background.coffee
+++ js/game/entities/Background.coffee
@@ -1,6 +1,7 @@
 define (require) ->

     Entity = require 'cs!game/entities/Entity'
+    easeout = require('cs!helper').easeout

     class Background extends Entity

@@ -12,9 +13,22 @@

             # Spritesheet de l'avion
             @layers = [
-                { speed: 10, alpha: 0.6, shift: 0, image: @scene.game.assets.backgroundMountainsB },
-                { speed: 20, alpha: 1,   shift: 0, image: @scene.game.assets.backgroundMountainsA },
-                { speed: 40, alpha: 0.6, shift: 0, image: @scene.game.assets.backgroundClouds },
+                {
+                    oncoming: 'bottom'
+                    speed: 10
+                    alpha: 0.6
+                    image: @scene.game.assets.backgroundMountainsB
+                }, {
+                    oncoming: 'bottom'
+                    speed: 20
+                    alpha: 1
+                    image: @scene.game.assets.backgroundMountainsA
+                }, {
+                    oncoming: 'top'
+                    speed: 40
+                    alpha: 0.6
+                    image: @scene.game.assets.backgroundClouds
+                }
             ]

         # Mise à jour
@@ -27,16 +41,26 @@
         handleDraw: (ctx) ->
             ctx.save()

+            l = 0
             for layer in @layers
                 @width = layer.image.width

-                x = -@t * layer.speed + layer.shift % @scene.width
+                x = -@t * layer.speed  % @scene.width
                 x -= @width while x + @width > 0

+                y = 0
+
+                # Première apparition animée des fonds
+                oncomingDuration = ++l * 0.5
+                y = easeout(@t, oncomingDuration, layer.image.height, -layer.image.height) if @t < oncomingDuration
+                y = -y if layer.oncoming == 'top'
+
+                # Configuration de l'opacité
                 ctx.globalAlpha = layer.alpha or 1

+                # Affichage du nombre de fonds nécessaire
                 while x < @scene.width
-                    ctx.drawImage(layer.image, x, @y)
+                    ctx.drawImage(layer.image, x, y)
                     x += @width

             ctx.restore()


More decimal places in "total cost" dashboard

Values in billions will be shown with one decimal place (previously
zero), while trillions will get two.

Closes #1651

--- app/assets/javascripts/lib/views/constraint_view.coffee
+++ app/assets/javascripts/lib/views/constraint_view.coffee
@@ -59,7 +59,9 @@

     out = switch key
       when 'total_energy_cost'
-        Metric.euros_to_string(result * 1000000000)
+        # One decimal place when billions, two when trillions.
+        precision = if result < 1000 then 1 else 2
+        Metric.euros_to_string(result * 1000000000, null, precision)
       when 'costs_fte'
         Metric.euros_to_string(result)
       when 'household_energy_cost'


use file UUIDs in group creation request

--- app/assets/javascripts/uploadcare/files/group.js.coffee
+++ app/assets/javascripts/uploadcare/files/group.js.coffee
@@ -129,7 +129,7 @@
         data =
           pub_key: @settings.publicKey
         for info, i in infos
-          data["files[#{i}]"] = info.cdnUrl
+          data["files[#{i}]"] = info.uuid
         $.ajax("#{@settings.urlBase}/group/", {data, dataType: 'jsonp'})
           .then(df.resolve, df.reject)
       return df.promise()


Добавил поддержку ender.js

Добавил поддержку ender.js

--- src/chocolate.coffee
+++ src/chocolate.coffee
@@ -462,7 +462,7 @@
     ]

     width = parseFloat element.css 'width'
-    $(styles).each (index, style) -> width += parseFloat element.css style
+    width += parseFloat element.css style for style in styles
     width

@@ -471,10 +471,8 @@
 # Экспорт в глобальное пространство
 window.chocolate = Chocolate

-# Подключение к jQuery Plugins
-if jQuery?.fn
-  jQuery.fn.chocolate = -> new Chocolate @, arguments[0]
-
-# Подключение к Zepto Plugins
-if Zepto?.fn
-  Zepto.fn.chocolate = -> new Chocolate @, arguments[0]+# Подключение к jQuery Plugins / Ender Plugins / Zepto Plugins
+frameworks = ['jQuery', 'ender', 'Zepto']
+for framework in frameworks
+  if window[framework]?.fn
+    window[framework].fn.chocolate = -> new Chocolate @, arguments[0]


Fixed an issue in the Exo.Model where default fields could not have 0 as a value.

--- model.coffee
+++ model.coffee
@@ -13,9 +13,10 @@

 		# Set default values
 		for attribute in @attributes
-			unless attribute in atts
-				atts[attribute] = @defaultValues[attribute] if @defaultValues[attribute]
-		super
+			unless atts[attribute]
+				atts[attribute.toString()] = @defaultValues[attribute.toString()] if @defaultValues[attribute.toString()] or @defaultValues[attribute.toString()] == 0
+
+		super atts, options

 	getClassName: ->
 		@constructor.className


Show the flag while a remote user is typing like in Google Docs

--- src/modules/multi-cursor.coffee
+++ src/modules/multi-cursor.coffee
@@ -61,8 +61,12 @@

   shiftCursors: (index, length, authorId = null) ->
     _.each(@cursors, (cursor, id) =>
-      return unless cursor and (cursor.index > index or cursor.userId == authorId)
-      cursor.index += Math.max(length, index - cursor.index)
+      return unless cursor
+      shift = Math.max(length, index - cursor.index)
+      if cursor.userId == authorId
+        this.moveCursor(authorId, cursor.index + shift)
+      else if cursor.index > index
+        cursor.index += shift
     )

   update: ->


update the unread count every 3 minutes

update the unread count every 3 minutes

--- js/app/app.coffee
+++ js/app/app.coffee
@@ -26,7 +26,7 @@
 	$provide.value 'Config', config =
 		markReadTimeout: 500
 		scrollTimeout: 500
-		feedUpdateInterval: 1000*60*5
+		feedUpdateInterval: 1000*60*3
 		itemBatchSize: 20
 		# the autoPageFactor defines how many heights of the box must be left
 		# before it starts autopaging e.g. if it was 2, then it will start


Patch options bug in coffee script

Patch options bug in coffee script

--- ng-qtip2.coffee
+++ ng-qtip2.coffee
@@ -60,7 +60,7 @@

       options.hide = scope.qtipHide if scope.qtipHide?
       options.show = scope.qtipShow if scope.qtipShow?
-      options = angular.extend {}, option, scope.qtipOptions if scope.qtipOption?
+      options = angular.extend {}, options, scope.qtipOptions if scope.qtipOptions?
       options.content = if content? then content else text: scope.qtipContent ? scope.qtip

       $(el).qtip options


refactored for cleanliness

refactored for cleanliness

--- spec/javascripts/comments_order_spec.js.coffee
+++ spec/javascripts/comments_order_spec.js.coffee
@@ -1,17 +1,14 @@
 describe "CommentsSettings", ->
-  scope = {}

   beforeEach module 'CommentsSettings'

   describe "reverseCommentsOrder", ->
     beforeEach inject ($rootScope) ->
-      scope = $rootScope
+      $rootScope.commentSettings = {}

-    it "applies to an attribute", inject ($compile) ->
-      scope.commentSettings = {}
-      #console.log("Scope commentsSettings is",scope)
-      $compile('

')(scope)
-      scope.$digest()
+    it "applies to an attribute", inject ($compile, $rootScope) ->
+      $compile('

')($rootScope)
+      $rootScope.$digest()

-      expect(scope.commentSettings).toBeDefined()
-      expect(scope.commentSettings.order).toEqual('reverse')
+      expect($rootScope.commentSettings).toBeDefined()
+      expect($rootScope.commentSettings.order).toEqual('reverse')


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/collision.coffee
+++ src/collision.coffee
@@ -215,7 +215,7 @@
     @returns {Boolean} true if the line intersects the rectangle, false otherwise
     ###
     rayRectangle: (source, direction, target) ->
-      unless target.
+      unless target.x

       xw = target.xw
       yw = target.yw


collision with regular bounds

collision with regular bounds

--- src/collision.coffee
+++ src/collision.coffee
@@ -221,7 +221,7 @@
           yw = target.height/2

           # Convert from bounds rect to centeredBounds rect
-          return Collision.rayRectangle source, direction,
+          return Collision.rayRectangle(source, direction,
             x: target.x + xw
             y: target.y + yw
             xw: xw


collaboration: controller add end for host

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -663,6 +663,20 @@
     @statusBar.emit 'CollaborationStarted'

+  endCollaborationForHost: (callbacks) ->
+
+    fileName = @getRealtimeFileName()
+    realtimeHelpers.deleteCollaborationFile @rtm, fileName, (err) =>
+      return callbacks.error err  if err
+      @setMachineSharingStatus off, (err) =>
+        return callbacks.error err  if err
+        socialHelpers.destroyChannel @socialChannel, (err) =>
+          return callbacks.error err  if err
+          envHelpers.detachSocialChannel @workspaceData, (err) =>
+            return callbacks.error err  if err
+            callbacks.success()
+
+
   showChat: ->

     switch @stateMachine.state


Fix indentation and wording in a comment

--- lib/assets/javascripts/sms_tools/message.js.coffee
+++ lib/assets/javascripts/sms_tools/message.js.coffee
@@ -45,9 +45,9 @@
     else
       parseInt Math.ceil(length / @maxLengthForEncoding[encoding].concatenated), 10

-   # Returns the number of symbols, which the given text will take up in an SMS
-   # message, taking into account any double-space symbols in the GSM 03.38
-   # encoding.
+  # Returns the number of symbols which the given text will eat up in an SMS
+  # message, taking into account any double-space symbols in the GSM 03.38
+  # encoding.
   _length: ->
     length = @text.length


2500ms poll intervals

2500ms poll intervals

--- sample.coffee
+++ sample.coffee
@@ -298,6 +298,7 @@
 if Meteor.isServer

    myJobs.setLogStream process.stdout
+   myJobs.promote 2500

    Meteor.startup () ->

@@ -448,5 +449,5 @@
             job.done()
             cb null

-      workers = myJobs.processJobs 'makeThumb', { concurrency: 2 }, worker
-
+      workers = myJobs.processJobs 'makeThumb', { concurrency: 2, pollInterval: 2500 }, worker
+


IMPROVED: startTimePerformance logging

IMPROVED: startTimePerformance logging

--- source/Art/Engine/Core/GlobalEpochCycle.coffee
+++ source/Art/Engine/Core/GlobalEpochCycle.coffee
@@ -78,6 +78,19 @@

     @addPerformanceSample name, timeResult - subTimeTotal

+  startTimePerformance: ->
+    start = currentSecond()
+    timerStack.push 0
+    start
+
+  endTimePerformance: (name, start) ->
+    subTimeTotal = timerStack.pop()
+    timeResult = currentSecond() - start
+    if (tsl = timerStack.length) > 0
+      timerStack[tsl-1] += timeResult
+
+    @addPerformanceSample name, timeResult - subTimeTotal
+
   @getter
     numActivePointers: ->
       count = 0
@@ -119,12 +132,35 @@
   attachCanvasElement: (toAddCe) ->
     @activeCanvasElements.push toAddCe

-  processFluxEpoch:  -> @timePerformance "flux"  , => fluxEpoch.processEpoch()
-  processIdleEpoch:  -> @timePerformance "idle"  , => idleEpoch?.processEpoch()
-  processEventEpoch: -> @timePerformance "event" , => eventEpoch.processEpoch()
-  processReactEpoch: -> @timePerformance "react" , => reactEpoch.processEpoch()
-  processStateEpoch: -> @timePerformance "aim"   , => stateEpoch.processEpoch()
-  processDrawEpoch:  -> @timePerformance "draw"  , => drawEpoch.processEpoch()
+  processFluxEpoch:  ->
+    start = @startTimePerformance()
+    fluxEpoch.processEpoch()
+    @endTimePerformance "flux", start
+
+  processIdleEpoch:  ->
+    start = @startTimePerformance()
+    idleEpoch?.processEpoch()
+    @endTimePerformance "idle", start
+
+  processEventEpoch: ->
+    start = @startTimePerformance()
+    eventEpoch.processEpoch()
+    @endTimePerformance "event", start
+
+  processReactEpoch: ->
+    start = @startTimePerformance()
+    reactEpoch.processEpoch()
+    @endTimePerformance "react", start
+
+  processStateEpoch: ->
+    start = @startTimePerformance()
+    stateEpoch.processEpoch()
+    @endTimePerformance "aim", start
+
+  processDrawEpoch:  ->
+    start = @startTimePerformance()
+    drawEpoch.processEpoch()
+    @endTimePerformance "draw", start

   flushEpochNow: ->
     return if @processingCycle


not to !

not to !


more readable in coffeescript

more readable in coffeescript

--- src/communist.coffee
+++ src/communist.coffee
@@ -1,33 +1,3 @@
-defaultFunc = """var _db = {}
-_db._add=function(name, func){
-	_db[name]=eval("("+func+")");
-	return true;
-};
-_db._rm=function(name){
-	delete _db[name];
-	return true;
-};
-_db._test=function(a){
-	return a || "all quiet";
-};
-var _f =  function () {
-    var args, method,__slice = [].slice;
-    method = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
-    return _db[method].apply(null, args);
-  };
- self.addEventListener('message', function(_e) {
- 	self.send = function(data){
- 		self.postMessage({
- 			messege:data,cb:_e.data.cb
- 		});
- 	};
-	self.postMessage({
- 		cb:_e.data.cb,
- 		body:_f.apply(null, _e.data.body)
- 		});
-
-});"""
-
 class Communist
 	constructor : (fun) ->
 		window.URL = window.URL or window.webkiURL
@@ -35,7 +5,35 @@
 			fun = fun.toString()
 			body = "var send;var _f =  #{ fun };self.addEventListener('message', function(_e) {send = function(data){self.postMessage({message:data,cb:_e.data.cb})};self.postMessage({body:_f.apply(null, _e.data.body),cb:_e.data.cb})})"
 		else
-			body = defaultFunc
+			body  = """var _db = {}
+				_db._add=function(name, func){
+				_db[name]=eval("("+func+")");
+					return true;
+				};
+				_db._rm=function(name){
+					delete _db[name];
+					return true;
+				};
+				_db._test=function(a){
+					return a || "all quiet";
+				};
+				var _f =  function () {
+				    var args, method,__slice = [].slice;
+				    method = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
+				    return _db[method].apply(null, args);
+				  };
+				 self.addEventListener('message', function(_e) {
+				 	self.send = function(data){
+				 		self.postMessage({
+ 							messege:data,cb:_e.data.cb
+ 						});
+ 					};
+					self.postMessage({
+ 						cb:_e.data.cb,
+ 						body:_f.apply(null, _e.data.body)
+ 						});
+
+				});"""
 		blob = new Blob [body],
 			type: "text/javascript"
 		bUrl = window.URL.createObjectURL(blob)


Add support to use multiple remotes from same remotehost

--- kites/sharedHosting/mounter.coffee
+++ kites/sharedHosting/mounter.coffee
@@ -36,13 +36,13 @@
     #   callback error
     #   callback null, result (information message)

-    {username, remotehost, remotepass, remotetype, storepass} = options
+    {username, remotehost, remotepass, remotetype, remoteuser, storepass} = options

     @readMountInfo options, (err,res)=>
       if err?
         callback err
       else
-        mounts = [mount for mount in res when mount.remotehost is remotehost][0]
+        mounts = [mount for mount in res when (mount.remotehost is remotehost and mount.remoteuser is remoteuser)][0]

         if mounts.length is 0 or res.length is 0
           callback "No such remote registered with '#{remotehost}' name."
@@ -83,7 +83,7 @@

     options.remoteuser or= 'anonymous'
     options.remotepass or= 'anonymous'
-    options.storepass    = yes if options.remotepass is 'anonymous'
+    options.storepass  or= yes if options.remotepass is 'anonymous'
     options.remotetype   = 'ftp'

     {username, remoteuser, remotepass, remotehost, storepass, mountonly} = options
@@ -92,7 +92,8 @@
       callback "Remote host is not provided"
       return no

-    options.mountpoint = escapePath path.join config.usersPath, username, config.baseMountDir, remotehost
+    options.mountpoint = escapePath path.join config.usersPath, username, config.baseMountDir, remoteuser
+    options.mountpoint+= "@#{escapePath remotehost}"

     unless safeForUser username, options.mountpoint
       console.error "User [#{username}] is trying to make something bad: ", options.mountpoint
@@ -107,36 +108,28 @@
       else
         @checkMountPoint options, (err, state)=>
           if state.mounted
-            callback "Mountpoint #{state.mountpoint} already exists"
-          else
-            @readMountInfo options, (err, mountlist)=>
-              if err?
+            callback "Remote host is already mounted to #{state.mountpoint}"
+          else
+            uid = res.trim()
+            log.info "[OK] user ID for user #{username} is #{uid}"
+            ftpfsopts = "#{config.ftpfs.opts},uid=#{uid},gid=#{uid},fsname=#{remotehost},user=#{remoteuser}:#{remotepass}"
+            @createMountpoint options, (err, res)=>
+              if err
                 callback err
               else
-                mounts = [mount for mount in mountlist when mount.remotehost is remotehost][0]
-                if mounts.length > 0 and not mountonly
-                  callback "Remotedrive #{remotehost} already exists."
-                else
-                  uid = res.trim()
-                  log.info "[OK] user ID for user #{username} is #{uid}"
-                  ftpfsopts = "#{config.ftpfs.opts},uid=#{uid},gid=#{uid},fsname=#{remotehost},user=#{remoteuser}:#{remotepass}"
-                  @createMountpoint options, (err, res)=>
-                    if err
-                      callback err
-                    else
-                      @spawnWrapper config.ftpfs.curlftpfs, ['-o', ftpfsopts, remotehost, options.mountpoint], (err, res)=>
-                        if err
-                          log.error error = "Couldn't mount remote FTP server #{remotehost}: #{err}"
-                          fs.rmdir options.mountpoint, (err)->
-                            log.error err if err
-                            callback error
-                        else
-                          @remountVE options, (err,res)=>
-                            if err
-                              callback err
-                            else
-                              @updateMountCfg options, (err,res)->
-                              callback null, res
+                @spawnWrapper config.ftpfs.curlftpfs, ['-o', ftpfsopts, remotehost, options.mountpoint], (err, res)=>
+                  if err
+                    log.error error = "Couldn't mount remote FTP server #{remotehost}: #{err}"
+                    fs.rmdir options.mountpoint, (err)->
+                      log.error err if err
+                      callback error
+                  else
+                    @remountVE options, (err,res)=>
+                      if err
+                        callback err
+                      else
+                        @updateMountCfg options, (err,res)->
+                        callback null, res

   # Safe
   checkMountPoint : (options, callback)->
@@ -149,17 +142,26 @@
     # return =
     #   mounted: Bool # true/false
     #   remotehost: String # remote FTP/SFTP hostname
+    #   remoteuser: String # remote FTP/SFTP username
     #   mountpoint: Strint # full path to the mountpoint

-    {username, remotehost} = options
-
-    options.mountpoint = escapePath path.join config.usersPath, username, config.baseMountDir, remotehost
+    {username, remoteuser, remotehost, mountpoint} = options
+
+    options.mountpoint = escapePath path.join config.usersPath, username, config.baseMountDir, remoteuser
+    options.mountpoint+= "@#{escapePath remotehost}"
+
+    console.log options

     unless safeForUser username, options.mountpoint
       console.error "User [#{username}] is trying to make something bad: ", options.mountpoint
       callback "You are not authorized to do this."
       return no

+    startsWith = (str, part)-> str.trim().indexOf(part) is 0
+    endsWith   = (str, part)-> str.trim().indexOf(part, str.trim().length - part?.length) isnt -1
+
+    res = {mounted : false, remotehost, mountpoint : options.mountpoint}
+
     rs = fs.createReadStream '/proc/mounts', flags: 'r'
     rs.setEncoding()

@@ -168,21 +170,18 @@
       callback error

     rs.on 'data', (data)->
-      if data.indexOf(options.mountpoint) isnt -1
-        console.log "[OK] #{remotehost} is mounted for user #{username}"
-        rs.destroy()
-        res =
-          mounted: true
-          remotehost: remotehost
-          mountpoint: options.mountpoint
-        callback null, res
-      else
-        console.log error = "[ERROR] mount point #{options.mountpoint} is not mounted"
-        res =
-          mounted: false
-          remotehost: remotehost
-          mountpoint: options.mountpoint
-        callback null, res
+      mounts = [mount.split(' ', 2) for mount in data.split('\n') when startsWith mount, remotehost][0]
+      if mounts.length > 0
+        state = [line for line in mounts when endsWith line[1], "#{remoteuser}@#{remotehost}"][0]
+        if state.length > 0
+          [remote, mountpoint] = state[0]
+          console.log "[OK] #{remotehost} is mounted for user #{username} to #{mountpoint}"
+          res = {mounted: true, remotehost, mountpoint}
+
+      console.log res
+      rs.destroy()
+
+      callback null, res

   # Safe
   umountDrive : (options, callback)->
@@ -195,20 +194,24 @@

     # options =
     #   username   : String # koding username
+    #   remoteuser : String # FTP username
     #   remotehost : String # FTP server address

-    {username, remotehost} = options
-
-    unless remotehost
-      callback "Remote host is not given"
-      return no
-
-    options.mountpoint = escapePath path.join config.usersPath, username, config.baseMountDir, remotehost
+    {username, remotehost, remoteuser, mountpoint} = options
+
+    if not remotehost or not remoteuser
+      callback "Remote host/user is not given"
+      return no
+
+    unless mountpoint
+      options.mountpoint = escapePath path.join config.usersPath, username, config.baseMountDir, remoteuser
+      options.mountpoint+= "@#{escapePath remotehost}"

     unless safeForUser username, options.mountpoint
       console.error "User [#{username}] is trying to make something bad: ", options.mountpoint
       callback "You are not authorized to do this."
       return no
+
     @spawnWrapper '/sbin/fuser', ['-mk',options.mountpoint],(err,res)=>
       @spawnWrapper '/bin/umount', [options.mountpoint],(err, res)=>
         if err?
@@ -294,7 +297,7 @@
           if err?
             callback err
           else
-            filteredMounts = [mount for mount in res when mount.remotehost is options.remotehost][0]
+            filteredMounts = [mount for mount in res when (mount.remotehost is options.remotehost and mount.remoteuser is options.remoteuser)][0]
             if filteredMounts.length is 0
               res.push newConf
               jsonConf = JSON.stringify res
@@ -332,7 +335,7 @@
     #   remotehost : String # remote server address
     #   username   : String # koding username

-    {remotehost, username} = options
+    {username, mountpoint} = options

     cfg = path.join config.usersPath, username, config.usersMountsFile

@@ -340,7 +343,7 @@
       if err?
         callback err
       else
-        filteredMounts = [mount for mount in res when mount.remotehost isnt remotehost][0]
+        filteredMounts = [mount for mount in res when mount.mountpoint isnt mountpoint][0]
         jsonConf = JSON.stringify filteredMounts or ''
         fs.writeFile cfg, jsonConf, 'utf-8',(err)->
           if err?
@@ -368,8 +371,9 @@

     {username, remoteuser, remotepass, remotehost, sshkey} = options

-    options.mountpoint =  path.join config.usersPath, username, config.baseMountDir, remotehost
-    keyPath = path.join config.usersPath,username,'.ssh','koding.pem'
+    options.mountpoint =  path.join config.usersPath, username, config.baseMountDir, remoteuser
+    options.mountpoint+= "@#{escapePath remotehost}"
+
     sshopts = [ "-o", "ssh_command=/usr/bin/ssh -i #{keyPath} -o #{config.sshfs.optsWithKey},fsname=#{remotehost}", "#{remoteuser}@#{remotehost}:/", options.mountpoint]
     console.log sshopts
     @createMountpoint options, (err, res)=>
@@ -407,7 +411,8 @@

     {username, remoteuser, remotepass, remotehost, sshkey} = options

-    options.mountpoint =  path.join config.usersPath, username, config.baseMountDir, remotehost
+    options.mountpoint =  path.join config.usersPath, username, config.baseMountDir, remoteuser
+    options.mountpoint+= "@#{escapePath remotehost}"

     sshopts = [ "-o", "ssh_command=/usr/bin/ssh -o #{config.sshfs.opts},fsname=#{remotehost}", "#{remoteuser}@#{remotehost}:/", options.mountpoint]
     console.log sshopts
@@ -530,7 +535,10 @@
     # options =
     #  mountpoint : String # mountpoint for remount drive

-    {mountpoint, username} = options
+    {remoteuser, remotehost, username} = options
+
+    mountpoint = escapePath path.join config.usersPath, username, config.baseMountDir, remoteuser
+    mountpoint+= "@#{escapePath remotehost}"

     unless mountpoint
       callback "Mountpoint is not provided"


huge success

huge success

--- svl.coffee
+++ svl.coffee
@@ -114,59 +114,71 @@
   data = dataSource.data

   plots = {
-    facet: (parent, size, data, {split, plot, x, y}, scales) ->
-      fsplit = (d) -> d[split]
-      splitData = sac(data, make_dimension(split), make_metric('$ident'))
-
-      buckets = sortedUniq(splitData.map(fsplit).sort())
-
-      verticalScale = d3.scale.ordinal()
-        .domain(buckets)
-        .rangeBands([0, size.height])
-
-      sel = parent.selectAll('g').data(splitData)
+    facet: (parent, size, data, {split, plot, x, y, color}) ->
+      fsplit = acc(split)
+      fx = acc(x)
+      fy = acc(y)
+      fcolor = acc(color)
+
+      sel = parent.selectAll('g')
+        .data((d,i) ->
+            if typeof data is 'function'
+              { data: myData, scales } = data.call(this,d,i)
+            else
+              myData = data
+              scales = {}
+
+            splitData = sac(myData, make_dimension(split), make_metric('$ident'))
+
+            scale = copy(scales)
+
+            buckets = sortedUniq(splitData.map(fsplit).sort())
+            scale.vertical = d3.scale.ordinal()
+              .domain(buckets)
+              .rangeBands([0, 1])
+            scale.vertical.by = fsplit
+
+            if fx
+              scale.x = d3.scale.linear()
+                .domain([d3.min(myData, fx), d3.max(myData, fx)])
+                .range([0, 1])
+              scale.x.by = fx
+
+            if fy
+              scale.y = d3.scale.linear()
+                .domain([d3.min(myData, fy), d3.max(myData, fy)])
+                .range([1, 0])
+              scale.y.by = fy
+
+            if fcolor
+              scale.color = d3.scale.category10()
+                .domain(myData.map(fcolor))
+              scale.color.by = fcolor
+
+            return cross_data_scales(splitData, scale)
+          )
+
+
       enterSel = sel.enter().append('g')
       enterSel.append('text').attr('dy', '1em')
       enterSel.append('g')

       sel.exit().remove()
       sel
-        .attr('transform', (d) -> "translate(0, #{verticalScale(fsplit(d))})")
+        .attr('transform', (d) -> s = d.s.vertical; "translate(0, #{s(s.by(d.d)) * size.height})")

       sel.select('text')
-        .text(fsplit)
+        .text((d) -> fsplit(d.d))

       labelWidth = 60
       innerGroup = sel.select('g')
         .attr('transform', "translate(#{labelWidth}, 0)")

-
-      fx = acc(x)
-      fy = acc(y)
-
-      myScales = []
-      myScales[0] = scale = {}
-
-      myData = data
-
-      if fx
-        scale.x = d3.scale.linear()
-          .domain([d3.min(myData, fx), d3.max(myData, fx)])
-          .range([0, 1])
-        scale.x.by = fx
-
-      if fy
-        scale.y = d3.scale.linear()
-          .domain([d3.min(myData, fy), d3.max(myData, fy)])
-          .range([1, 0])
-        scale.y.by = fy
-
-
       if plot
         doPlot(
           innerGroup
-          {width: size.width-labelWidth, height: size.height/buckets.length}
-          (d) -> { data:d.$ident, scales:myScales[0] }
+          {width: size.width-labelWidth, height: size.height/4}
+          (d) -> { data:d.d.$ident, scales:d.s }
           plot
         )
       return
@@ -203,7 +215,6 @@
                 .domain(myData.map(fcolor))
               scale.color.by = fcolor

-            console.log i, scale
             return cross_data_scales(myData, scale)
           )

@@ -214,8 +225,7 @@
         .attr('cy', (d) -> s = d.s.y; s(s.by(d.d)) * size.height)
         .attr('r', 3.5)

-      if fcolor
-        sel.style('fill', (d) -> s = d.s.color; s(s.by(d.d)))
+      sel.style('fill', (d) -> s = d.s.color; if s then s(s.by(d.d)) else null)

       return

@@ -318,14 +328,14 @@
     removeNA: false
   plot:
     type: 'facet'
-    split: 'Letter'
+    split: 'Number'
     plot:
       type: 'facet'
-      split: 'Number'
+      split: 'Letter'
+      x: 'Time'
+      color: 'Letter'
       plot:
         type: 'points'
-        color: 'Letter'
-        x: 'Time'
         y: 'ScoreA'
 }


ENHANCE: Ensure can apply for extensions checks unit config

--- src/app/common/services/project-service.coffee
+++ src/app/common/services/project-service.coffee
@@ -249,7 +249,7 @@

     # You can apply for an extension in certain states, if it is before the deadline or was submitted before the deadline, and you can request some extensions still.
     task.canApplyForExtension = ->
-      task.inStateThatAllowsExtension() && ( !task.isPastDeadline() || task.wasSubmittedOnTime() ) && task.maxWeeksCanExtend() > 0
+      task.unit().allow_student_extension_requests && task.inStateThatAllowsExtension() && ( !task.isPastDeadline() || task.wasSubmittedOnTime() ) && task.maxWeeksCanExtend() > 0

     task.inFinalState = ->
       task.status in taskService.finalStatuses


Clear visual errors after code change (#304)

--- ozaria/site/views/play/level/tome/SpellView.coffee
+++ ozaria/site/views/play/level/tome/SpellView.coffee
@@ -723,9 +723,9 @@
       _.throttle @notifySpellChanged, 300
       _.throttle @updateLines, 500
       _.throttle @hideProblemAlert, 500
+      _.throttle @clearAetherDisplay.bind(@), 250
     ]
-    unless @options.level.get('ozariaType') == 'capstone'
-      onAnyChange.push(_.debounce(@updateAether, 500))
+
     onSignificantChange.push _.debounce @checkRequiredCode, 750 if @options.level.get 'requiredCode'
     onSignificantChange.push _.debounce @checkSuspectCode, 750 if @options.level.get 'suspectCode'
     onAnyChange.push _.throttle @updateHTML, 10 if @options.level.isType('web-dev')


ensure max-time is set.

ensure max-time is set.

@tenedor

--- coffee/src/views/time_range_input_view.coffee
+++ coffee/src/views/time_range_input_view.coffee
@@ -166,6 +166,7 @@
     time = @_end - @_start
     time = if _.isNaN time then '--' else timestring time
     @$('.total-time').text time
+    @$('span.max-time').text acorn.util.Time.secondsToTimestring @_max

   _bound: (val) =>


Updates the call to dashboard poller in this jasmine test

--- dashboard_spec.coffee
+++ dashboard_spec.coffee
@@ -10,7 +10,7 @@
     it "should return the dashboard data", ->
       that = this

-      @dashboard.getData 'lesson_leaders', {}, (data) ->
+      @dashboard.getData { type: 'lesson_leaders' }, (data) ->
         that.data = data

       request = mostRecentAjaxRequest()


added verbose option to all commands

added verbose option to all commands

--- natal.coffee
+++ natal.coffee
@@ -16,6 +16,8 @@
 pkgJson = require __dirname + '/package.json'

 globalVerbose   = false
+verboseFlag     = '-v, --verbose'
+verboseText     = 'verbose output'
 nodeVersion     = pkgJson.engines.node
 resources       = __dirname + '/resources/'
 validNameRx     = /^[A-Z][0-9A-Z]*$/i
@@ -46,6 +48,12 @@
   process.exit 1

+verboseDec = (fn) ->
+  (..., cmd) ->
+    globalVerbose = cmd.verbose
+    fn.apply cli, arguments
+
+
 exec = (cmd, keepOutput) ->
   if globalVerbose and !keepOutput
     return child.execSync cmd, stdio: 'inherit'
@@ -169,9 +177,7 @@
     logErr message

-init = (projName, interfaceName, isVerbose) ->
-  globalVerbose = isVerbose
-
+init = (projName, interfaceName) ->
   log "Creating #{projName}", 'bgMagenta'
   log ''

@@ -489,9 +495,9 @@

 cli.command 'init <name>'
   .description 'create a new ClojureScript React Native project'
+  .option verboseFlag, verboseText
   .option "-i, --interface [#{interfaceNames.join ' '}]", 'specify React interface'
-  .option '-v, --verbose', 'verbose output'
-  .action (name, cmd) ->
+  .action verboseDec (name, cmd) ->
     if cmd
       interfaceName = cmd['interface'] or defaultInterface
     else
@@ -507,25 +513,28 @@
              natal init HelloWorld
              '''

-    ensureFreePort -> init name, interfaceName, cmd.verbose
+    ensureFreePort -> init name, interfaceName

 cli.command 'launch'
   .description 'compile project and run in simulator'
-  .action ->
+  .option verboseFlag, verboseText
+  .action verboseDec ->
     ensureFreePort -> launch readConfig()

 cli.command 'repl'
   .description 'launch a ClojureScript REPL with background compilation'
+  .option verboseFlag, verboseText
   .option '-c, --choose', 'choose target device from list'
-  .action (cmd) ->
+  .action verboseDec (cmd) ->
     startRepl readConfig().name, !cmd.choose

 cli.command 'listdevices'
   .description 'list available simulator devices by index'
-  .action ->
+  .option verboseFlag, verboseText
+  .action verboseDec ->
     console.log (getDeviceList()
       .map (line, i) -> "#{i}\t#{line.replace /\[.+\]/, ''}"
       .join '\n')
@@ -533,7 +542,8 @@

 cli.command 'setdevice <index>'
   .description 'choose simulator device by index'
-  .action (index) ->
+  .option verboseFlag, verboseText
+  .action verboseDec (index) ->
     unless device = getDeviceList()[parseInt index, 10]
       logErr 'Invalid device index. Run natal listdevices for valid indexes.'

@@ -544,13 +554,15 @@

 cli.command 'xcode'
   .description 'open Xcode project'
-  .action ->
+  .option verboseFlag, verboseText
+  .action verboseDec ->
     openXcode readConfig().name

 cli.command 'deps'
   .description 'install all dependencies for the project'
-  .action ->
+  .option verboseFlag, verboseText
+  .action verboseDec ->
     try
       log 'Installing Leiningen jars'
       exec 'lein deps'


Bug where I used '?q=blah' instead of '?question=blah'

--- script.coffee
+++ script.coffee
@@ -4,7 +4,7 @@
         jQuery("#answer").attr("class", "answered")
         jQuery(".unanswered").attr("class", "none")
         question = jQuery("#question").val()
-        jQuery("#externallink").attr("href", "http://decisiverobot.com/?q=#{question}")
+        jQuery("#externallink").attr("href", "http://decisiverobot.com/?question=#{question}")

     answer = () ->
         question = jQuery("#question").val()


Adiciona classe em label de sku selector (para loja Pedala)

--- src/coffee/portal-sku-selector.coffee
+++ src/coffee/portal-sku-selector.coffee
@@ -171,6 +171,7 @@
 				.addClass("dimension-#{dimension.nameSanitized}")
 				.addClass("espec_#{dimension.index}")
 				.addClass("skuespec_#{valueSanitized}")
+				.addClass("skuespec_#{spacesToHyphens(dimension.name)}_opcao_#{spacesToHyphens(value)}")

 			skuList.appendTo(list)

@@ -471,6 +472,8 @@
 		obj2[k] = f k, v
 	obj2

+spacesToHyphens = (str) ->
+	str.replace(/\ /g, '-')

 #
 # EXPORTS


The $oberver was not needed.

The $oberver was not needed.

--- coffee/codesearch.coffee
+++ coffee/codesearch.coffee
@@ -5,6 +5,8 @@

     $scope.results = resultsService
     $scope.modal = modalService
+    $scope.search =
+        term: ""
     return

@@ -66,20 +68,18 @@

     (scope, element, attrs) ->

-        attrs.$observe "popup", ->
-            element.on "click", (event) ->
-                event.preventDefault()
+        element.on "click", (event) ->
+            event.preventDefault()

-                result = $http
-                    method: "GET",
-                    url: attrs.codesearchPopup
+            result = $http
+                method: "GET",
+                url: attrs.codesearchPopup

-                result.success (data) ->
-                    modalService.setTitle data.title
-                    modalService.setBody data.body
-                    modalService.setUrl data.url
-                    modalService.show()
-                    return
+            result.success (data) ->
+                modalService.setTitle data.title
+                modalService.setBody data.body
+                modalService.setUrl data.url
+                modalService.show()
                 return
             return
         return


Add basic neutralizing and capturing of beacons

Neutralizing and capturing works, when someone enters/leaves the area
while capturing/neutralizing it will not handle it correctly yet.

--- server.coffee
+++ server.coffee
@@ -29,6 +29,7 @@
 	Db.shared.set 'game', 'beacons', location.lat.toString()+'_'+location.lng.toString(), {location: location}
 	Db.shared.set 'game', 'beacons', location.lat.toString()+'_'+location.lng.toString(), 'owner', -1
 	Db.shared.set 'game', 'beacons', location.lat.toString()+'_'+location.lng.toString(), 'nextOwner', -1
+	Db.shared.set 'game', 'beacons', location.lat.toString()+'_'+location.lng.toString(), 'percentage', 0
 	Db.shared.set 'game', 'beacons', location.lat.toString()+'_'+location.lng.toString(), 'captureValue', 10

 exports.client_deleteMarker = (location) ->
@@ -74,6 +75,8 @@
 		team = 0 if team >= teams
 	Db.shared.iterate 'game', 'teams', (team) !->
 		Db.shared.set 'game', 'teams', team.n, 'teamScore', 0
+		Db.shared.set 'game', 'teams', team.n, 'captured', 0
+		Db.shared.set 'game', 'teams', team.n, 'neutralized', 0
 	Db.shared.set 'gameState', 1 # Set gameState at the end, because this triggers a repaint at the client so we want all data prepared before that

 # Checkin location for capturing a beacon
@@ -128,65 +131,101 @@
 					if competing.length == 1
 						# Team will capture the flag
 						activeTeam = competing[0]
-						beacon.set 'nextOwner', activeTeam
-						if owner == -1
-							# Capturing
-							log 'Team ', activeTeam, ' is capturing beacon ', beacon.n
-
-
+						if activeTeam != owner
+							beacon.set 'nextOwner', activeTeam
+							percentage = beacon.get 'percentage'
+							if owner == -1
+								# Capturing
+								log 'Team ', activeTeam, ' is capturing beacon ', beacon.n
+								# Set timer for capturing
+								playersStr = getInrangePlayers(beacon.n)
+								Timer.set (100-percentage)*10*30, 'onCapture', {beacon: beacon.n, players: playersStr}
+							else
+								# Neutralizing
+								log 'Team ', activeTeam, ' is neutralizing beacon ', beacon.n
+
+								playersStr = getInrangePlayers(beacon.n)
+								Timer.set percentage*10*30, 'onNeutralize', {beacon: beacon.n, players: playersStr}
 						else
-							# Neutralizing
-							log 'Team ', activeTeam, ' is neutralizing beacon ', beacon.n
+							log 'activeteam already has the beacon, ', activeTeam, '=', owner

 					else if competing.length > 1
 						# No progess, stand-off
 						log 'Capture of beacon ', beacon.n, ' on hold, competing teams: ', competing
-
-
-
-
-					# START debug code
-					log 'beacon ', beacon.n, ' captured by team ', getTeamOfUser(client), ' by user ', client
-					beacon.set 'owner', getTeamOfUser(client)
-					# END debug code
-
-					#  list capture
-					maxId = Db.shared.ref('game', 'eventlist').incr 'maxId'
-					Db.shared.set 'game', 'eventlist', maxId, 'timestamp', new Date()/1000
-					Db.shared.set 'game', 'eventlist', maxId, 'type', "capture"
-					Db.shared.set 'game', 'eventlist', maxId, 'beacon', beacon.n
-					Db.shared.set 'game', 'eventlist', maxId, 'conqueror', getTeamOfUser(client)
-
-
-					# create capture event
-					# To Do: personalize for team members or dubed players
-					Event.create
-    					unit: 'capture'
-    					text: "Team " + Db.shared.get('colors', getTeamOfUser(client) , 'name') + " captured a beacon"
-					# calculate change in scores
-					# TO DO
-
-
-
-					Db.shared.modify 'game', 'teams', getTeamOfUser(client), 'users', client, "userScore", (v) -> v+beacon.get("captureValue")
-					Db.shared.modify 'game', 'teams', getTeamOfUser(client), 'teamScore', (v) -> v+beacon.get("captureValue")
-					beacon.modify "captureValue", (v) ->
-						if v > 1
-							return v-1
-						else
-							return v
-
 					# Start takeover
 				else
 					log 'Removed from inRange: ', client, ', name=', Plugin.userName(client)
 					# clean takeover
 					beacon.remove 'inRange', client

+# Called by the beacon capture timer
+exports.onCapture = (args) !->
+	beacon = Db.shared.ref 'game', 'beacons', args.beacon
+	nextOwner = beacon.get('nextOwner')
+	log 'Team ', nextOwner, ' has captured beacon ', beacon.n, ', players: ', args.players
+	beacon.set 'percentage', 100
+	beacon.set 'owner', nextOwner
+
+	# Add event log entrie(s)
+	maxId = Db.shared.ref('game', 'eventlist').incr 'maxId'
+	Db.shared.set 'game', 'eventlist', maxId, 'timestamp', new Date()/1000
+	Db.shared.set 'game', 'eventlist', maxId, 'type', "capture"
+	Db.shared.set 'game', 'eventlist', maxId, 'beacon', beacon.n
+	Db.shared.set 'game', 'eventlist', maxId, 'conqueror', nextOwner
+
+	# Handle push notifications
+	# TODO: Personalize for team members or dubed players
+	# TODO: Handle changes in ranking
+	Event.create
+		unit: 'capture'
+		text: "Team " + Db.shared.get('colors', nextOwner , 'name') + " captured a beacon"
+
+	# Handle points and statistics
+	# TODO: Split points evenly instead of giving to all players inRange
+	for player in args.players.split(', ')
+		Db.shared.modify 'game', 'teams', nextOwner, 'users', player, "userScore", (v) -> v+beacon.get("captureValue")
+	Db.shared.modify 'game', 'teams', nextOwner, 'teamScore', (v) -> v+beacon.get("captureValue")
+	Db.shared.modify 'game', 'teams', nextOwner, 'captured', (v) -> v+1
+	beacon.modify "captureValue", (v) ->
+		if v > 1
+			return v-1
+		else
+			return v
+
+exports.onNeutralize = (args) !->
+	beacon = Db.shared.ref 'game', 'beacons', args.beacon
+	neutralizer = beacon.get('nextOwner')
+	log 'Team ', neutralizer, ' has neutralized beacon ', beacon.n, ', players: ', args.players
+	beacon.set 'percentage', 0
+	beacon.set 'owner', -1
+
+	# Handle points and statistics
+	Db.shared.modify 'game', 'teams', args.neutralizer, 'neutralized', (v) -> v+1
+
+
+
+	# Handle capturing
+	percentage = beacon.get 'percentage'
+	log 'Team ', neutralizer, ' is capturing beacon ', beacon.n, ' (after neutralize)'
+	# Set timer for capturing
+	Timer.set (100-percentage)*10*30, 'onCapture', args
+
 #Function called when the game ends
 exports.endGame = !->
 	log "The game ended!"

 # ========== Functions ==========
+# Get a string of the players that are inRange of a beacon
+getInrangePlayers = (beacon) ->
+	playersStr = undefined
+	Db.shared.iterate 'game', 'beacons', beacon, 'inRange', (player) !->
+		if playersStr?
+			playersStr = playersStr + ', ' + player.n
+		else
+			playersStr = player.n
+	log 'players string: ', playersStr
+	return playersStr
+
 # Setup an empty game
 initializeGame = ->
 	Db.shared.set 'gameState', 0


Actually return the server and not the http object.

--- server.coffee
+++ server.coffee
@@ -11,9 +11,9 @@
 module.exports.startServer = (port, path, callback) ->
   app = createAndConfigureApp()
   port = port or app.get('port')
-  http.createServer(app).listen(port, callback)
+  server = http.createServer(app).listen(port, callback)
   log.info('Express SSL server listening on port ' + port)
-  http
+  server

 createAndConfigureApp = ->
   serverSetup.setupLogging()


Missed delimiter.

Missed delimiter.

$ is invalid in the collection name so it is a good delimiter.

--- server.coffee
+++ server.coffee
@@ -14,7 +14,7 @@
     publish.added = (collectionName, id, fields) ->
       stringId = @_idFilter.idStringify id

-      FiberUtils.synchronize guardObject, "#{collectionName}#{stringId}", =>
+      FiberUtils.synchronize guardObject, "#{collectionName}$#{stringId}", =>
         return originalAdded.call @, collectionName, id, fields unless disabled

         collectionView = @_session.getCollectionView collectionName
@@ -36,7 +36,7 @@
     publish.changed = (collectionName, id, fields) ->
       stringId = @_idFilter.idStringify id

-      FiberUtils.synchronize guardObject, "#{collectionName}#{stringId}", =>
+      FiberUtils.synchronize guardObject, "#{collectionName}$#{stringId}", =>
         return originalChanged.call @, collectionName, id, fields unless disabled

         collectionView = @_session.getCollectionView collectionName
@@ -63,7 +63,7 @@
     publish.removed = (collectionName, id) ->
       stringId = @_idFilter.idStringify id

-      FiberUtils.synchronize guardObject, "#{collectionName}#{stringId}", =>
+      FiberUtils.synchronize guardObject, "#{collectionName}$#{stringId}", =>
         return originalRemoved.call @, collectionName, id unless disabled

         collectionView = @_session.getCollectionView collectionName


Fixed tracking of renaming.

Fixed tracking of renaming.

Fixes #2.

--- server.coffee
+++ server.coffee
@@ -417,8 +417,6 @@
       else
         throw new Error "Inconsistent migration recorded, expected migrationName='#{migration.name}', oldCollectionName='#{currentName}', newCollectionName='#{newName}', oldVersion='#{currentSchema}', newVersion='#{newSchema}', got: #{util.inspect migrations[0], depth: 10}"
     else if migrationsPending is Number.POSITIVE_INFINITY
-      # This is the collection name recorded as the last, so we start with it.
-      initialName = currentName
       migrationsPending = i

     currentSchema = newSchema


add jquery

add jquery

--- karma.conf.coffee
+++ karma.conf.coffee
@@ -14,6 +14,7 @@
     files: [
       'https://ajax.googleapis.com/ajax/libs/angularjs/1.2.4/angular.js'
       'https://ajax.googleapis.com/ajax/libs/angularjs/1.2.4/angular-mocks.js',
+      'https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js',
       '../../coolforms.js',
       'services/*.coffee',
       'validators/*.coffee'


fixed indentation

fixed indentation

--- src/components/task-plan/reading.cjsx
+++ src/components/task-plan/reading.cjsx
@@ -34,9 +34,9 @@
     topic = TocStore.getSectionInfo(@props.topicId)

     if @props.index
-        moveUpButton = <BS.Button onClick={@moveReadingUp} className="btn-xs -move-reading-up">
-          <i className="fa fa-arrow-up"/>
-        </BS.Button>
+      moveUpButton = <BS.Button onClick={@moveReadingUp} className="btn-xs -move-reading-up">
+        <i className="fa fa-arrow-up"/>
+      </BS.Button>

     <li className='selected-section'>
       <ChapterSection section={topic.chapter_section}/>


resolve local paths

resolve local paths

--- parser/swagger-transformer.coffee
+++ parser/swagger-transformer.coffee
@@ -1,12 +1,13 @@
 _ = require 'lodash'
 fs = require 'fs'
+path = require 'path'
 swagger2 = require('swagger-tools').specs.v2
 Swagger2ToMessageSchema = require './swagger-2-to-message-schema'
 Swagger2ToProxyConfig = require './swagger-2-to-proxy-config'

 class SwaggerTransformer
   toMessageSchema: (filePath, callback=->) =>
-    fs.readFile filePath, 'utf8', (error, swaggerFile) =>
+    fs.readFile path.resolve(filePath), 'utf8', (error, swaggerFile) =>
       return callback error if error?
       swagger2.resolve JSON.parse(swaggerFile), (error, swagger) =>
         return callback error if error?
@@ -14,7 +15,7 @@
         callback null, swaggerTransformer.transform()

   toProxyConfig: (filePath, callback=->) =>
-    fs.readFile filePath, 'utf8', (error, swaggerFile) =>
+    fs.readFile path.resolve(filePath), 'utf8', (error, swaggerFile) =>
       return callback error if error?
       swagger2.resolve JSON.parse(swaggerFile), (error, swagger) =>
         return callback error if error?


initial stab for testing routing thingy

initial stab for testing routing thingy

--- src/extras/testing/controller_test_case.coffee
+++ src/extras/testing/controller_test_case.coffee
@@ -1,2 +1,32 @@
 class Batman.ControllerTestCase extends Batman.TestCase
   @mixin Batman.ModelExpectations
+
+  assertRoutes: (params = {}) ->
+    @controllerClass ||= Batman.currentApp[@constructor.name.replace(/Test/, '')]
+    @assert @controllerClass, "Couldn't deduce controller name from test case, please assign it via @controllerClass"
+
+    controller = new @controllerClass
+    @assert controller.routingKey, "Routing key isn't set"
+
+    actionRoutes = Batman.currentApp.get('routes.routeMap').childrenByName[controller.routingKey]
+    @assert actionRoutes, "No routes for routing key: #{controller.routingKey}"
+
+    assertedActions = []
+    for action in actionRoutes.childrenByOrder
+      @assertAction(controller, action, params.action)
+      assertedActions.push(action.action)
+
+    #unnamed routes, user specified actions to run through dispatch
+    for action of params
+      if not action in assertedActions
+        @assertedAction(controller, { action: action }, params.action )
+
+  assertAction: (controller, actionRoute, params = {}) ->
+    @assertEqual 'function', typeof controller[actionRoute.action], "Action: #{actionRoute.action} doesn't exist!"
+    for namedArg in actionRoute.namedArguments
+      @assert params.params[namedArg], "named argument: #{namedArg} doesn't exist in parameters"
+
+    params.preAction?()
+    controller.dispatch actionRoute.action, params.params
+    params.postAction?()
+    null


Minor improvement to Logger, couple of comments.

--- src/Log.coffee
+++ src/Log.coffee
@@ -25,11 +25,13 @@
     code = @_levels[level]
     return typeof code != 'undefined' && code <= @_levels[currentLevel]

+  # Enables the Log functions if they have been disabled.
   on: ->
     return if @_loggingOn
     @_loggingOn = true
     _.each Object.keys(FunctionReferences), (f) => @[f] = FunctionReferences[f]

+  # Disables the Log functions.
   off: ->
     return unless @_loggingOn
     @_loggingOn = false
@@ -43,15 +45,22 @@

   error: -> @shouldLog('error') && Log.msg('ERROR', arguments, console.error)

-  msg: (msg, args, func) ->
+  # Prints the message in `args` to the console function `func` (defaults to `console.log`),
+  # prepended with the string `[channel]`.
+  msg: (channel, args, func) ->
     return if @level == 'off'
     func = Setter.defaultValue(func, console.log)
+    # Ensure the message string is at least five characters for prettier printing.
+    if channel.length < 5
+      channel += Array(6 - channel.length).join(' ')
+
     args = Array.prototype.slice.call(args)
-    args.splice(0, 0, '[' + msg + '] ')
+    args.splice(0, 0, '[' + channel + '] ')
     func.apply(console, args)
     # Logging should have no return.
     return undefined

+  # Prints a stack trace from the caller's point of execution.
   trace: ->
     e = new Error('dummy')
     stack = e.stack.replace(/^[^\(]+?[\n$]/gm, '')
@@ -94,14 +103,13 @@
 global = @
 console = global.console

-# For those without a console
+# For those without a console.
 if typeof console == 'undefined'
   console = {}
-  funcs = ['log', 'debug', 'error', 'warn', 'time', 'timeEnd']
   _.each FunctionReferences, (func, funcName) ->
     # Ignorance is bliss
     console[funcName] = ->

 if Meteor?
-  # Log is already defined in the logging pacakge, so we define a different global variable for now.
+  # Log is already defined in the logging package, so we define a different global variable for now.
   Logger = Log


fix: copy select2 assets to release

fix: copy select2 assets to release


levels and default game states

levels and default game states

--- src/game_over.coffee
+++ src/game_over.coffee
@@ -11,7 +11,7 @@

     self.centerText canvas, "Game Over"

-    self.centerText canvas, "Press any key to start",
+    self.centerText canvas, "Press any key to restart",
       size: 12
       y: App.height / 2 + 30


Gruntfile: colorize logging.

Gruntfile: colorize logging.

--- src/gruntfile.coffee
+++ src/gruntfile.coffee
@@ -7,6 +7,8 @@
 types = require 'ast-types'
 util = require 'util'
 chalk = require 'chalk'
+
+base = require './base'

 module.exports = TamiaGruntfile = (filename='Gruntfile.js') ->
 	# Copy template if file don’t exist
@@ -44,15 +46,16 @@

 	@_configObject = configObject

-TamiaGruntfile::writeln = (text) ->
-	console.log (chalk.green 'Gruntfile:') + " #{text}"
+TamiaGruntfile::writeln = (msg) ->
+	msg = msg.replace(/`(.*?)`/g, (m, str) -> chalk.cyan(str))
+	console.log (chalk.green 'Gruntfile:') + " #{msg}"

 TamiaGruntfile::addTask = (task, config) ->
 	added = !@tasks[task]
 	gfc.Gruntfile::addTask.call(this, task, config)
-	@writeln "task '#{task}' added"  if added
+	@writeln "task `#{task}` added"  if added

 TamiaGruntfile::registerTask = (name, tasks) ->
 	registered = true
 	gfc.Gruntfile::registerTask.call(this, name, tasks)
-	@writeln tasks.join(', ') + " added to '#{name}' alias"  if registered
+	@writeln '`' + tasks.join('`, `') + "` added to `#{name}` alias"  if registered


Tried to ease type errors but still problems inside macros and removed the fix for command line, that stuff is still borken

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -831,9 +831,9 @@
 uniformCollectionItemsCompile = (ctx, items) ->
   itemType = ctx.freshTypeVariable star
   compiledItems = termsCompile ctx, items
-  for item in items
+  for item in items when item.tea
     unify ctx, itemType, item.tea.type
-  constraints: (concatMap _constraints, (tea for {tea} in items))
+  constraints: (concatMap _constraints, (tea for {tea} in items when tea))
   itemType: itemType
   compiled: compiledItems


Adding msg.match

Adding msg.match

--- src/bgg.coffee
+++ src/bgg.coffee
@@ -14,21 +14,19 @@
 # Author:
 #   Bart Dorsey[@<org>]

-options = {
-  timeout: 10000,
+options =
+  timeout: 10000

-  retry: {
-    initial: 100,
-    multiplier: 2,
+  retry:
+    initial: 100
+    multiplier: 2
     max: 15e3
-  }
-}

 bgg = require('bgg')(options)

 module.exports = (robot) ->
   robot.respond /bgg search (.*)/, (msg) ->
-    bgg('search', { query: ""})
+    bgg('search', query: msg.match[1])
       .then (results) ->
         msg.reply "hello!"
         console.log(results)