Update test data after merging in other changes

--- Src/coffeescript/cql-execution/test/elm/clinical/data.coffee
+++ Src/coffeescript/cql-execution/test/elm/clinical/data.coffee
@@ -135,8 +135,14 @@

 ###
 Translation Error(s):
-[10:69, 10:69] mismatched input 'version' expecting {'min', 'd', 'ms', 's', 'a', 'mo', 'h', IDENTIFIER, QUOTEDIDENTIFIER}
-[15:74, 15:74] mismatched input 'version' expecting {'min', 'd', 'ms', 's', 'a', 'mo', 'h', IDENTIFIER, QUOTEDIDENTIFIER}
+[9:20, 9:71] Internal translator error.
+[9:20, 9:83] Could not determine signature for invocation of operator System.InValueSet.
+[10:18, 10:89] Internal translator error.
+[10:18, 10:101] Could not determine signature for invocation of operator System.InValueSet.
+[14:25, 14:76] Internal translator error.
+[14:25, 14:88] Could not determine signature for invocation of operator System.InValueSet.
+[15:23, 15:94] Internal translator error.
+[15:23, 15:106] Could not determine signature for invocation of operator System.InValueSet.
 ###
 module.exports['InValueSet'] = {
    "library" : {
@@ -238,64 +244,14 @@
             "context" : "Patient",
             "accessLevel" : "Public",
             "expression" : {
-               "type" : "InValueSet",
-               "code" : {
-                  "classType" : "{urn:hl7-org:elm:r1}Code",
-                  "type" : "Instance",
-                  "element" : [ {
-                     "name" : "code",
-                     "value" : {
-                        "valueType" : "{urn:hl7-org:elm:r1}String",
-                        "value" : "F",
-                        "type" : "Literal"
-                     }
-                  }, {
-                     "name" : "system",
-                     "value" : {
-                        "valueType" : "{urn:hl7-org:elm:r1}String",
-                        "value" : "2.16.840.1.113883.18.2",
-                        "type" : "Literal"
-                     }
-                  } ]
-               },
-               "valueset" : {
-                  "name" : "Female"
-               }
+               "type" : "Null"
             }
          }, {
             "name" : "LongCode",
             "context" : "Patient",
             "accessLevel" : "Public",
             "expression" : {
-               "type" : "InValueSet",
-               "code" : {
-                  "classType" : "{urn:hl7-org:elm:r1}Code",
-                  "type" : "Instance",
-                  "element" : [ {
-                     "name" : "code",
-                     "value" : {
-                        "valueType" : "{urn:hl7-org:elm:r1}String",
-                        "value" : "F",
-                        "type" : "Literal"
-                     }
-                  }, {
-                     "name" : "system",
-                     "value" : {
-                        "valueType" : "{urn:hl7-org:elm:r1}String",
-                        "value" : "2.16.840.1.113883.18.2",
-                        "type" : "Literal"
-                     }
-                  }, {
-                     "value" : {
-                        "valueType" : "{urn:hl7-org:elm:r1}String",
-                        "value" : "HL7V2.5",
-                        "type" : "Literal"
-                     }
-                  } ]
-               },
-               "valueset" : {
-                  "name" : "Female"
-               }
+               "type" : "Null"
             }
          }, {
             "name" : "WrongString",
@@ -354,64 +310,14 @@
             "context" : "Patient",
             "accessLevel" : "Public",
             "expression" : {
-               "type" : "InValueSet",
-               "code" : {
-                  "classType" : "{urn:hl7-org:elm:r1}Code",
-                  "type" : "Instance",
-                  "element" : [ {
-                     "name" : "code",
-                     "value" : {
-                        "valueType" : "{urn:hl7-org:elm:r1}String",
-                        "value" : "F",
-                        "type" : "Literal"
-                     }
-                  }, {
-                     "name" : "system",
-                     "value" : {
-                        "valueType" : "{urn:hl7-org:elm:r1}String",
-                        "value" : "3.16.840.1.113883.18.2",
-                        "type" : "Literal"
-                     }
-                  } ]
-               },
-               "valueset" : {
-                  "name" : "Female"
-               }
+               "type" : "Null"
             }
          }, {
             "name" : "WrongLongCode",
             "context" : "Patient",
             "accessLevel" : "Public",
             "expression" : {
-               "type" : "InValueSet",
-               "code" : {
-                  "classType" : "{urn:hl7-org:elm:r1}Code",
-                  "type" : "Instance",
-                  "element" : [ {
-                     "name" : "code",
-                     "value" : {
-                        "valueType" : "{urn:hl7-org:elm:r1}String",
-                        "value" : "F",
-                        "type" : "Literal"
-                     }
-                  }, {
-                     "name" : "system",
-                     "value" : {
-                        "valueType" : "{urn:hl7-org:elm:r1}String",
-                        "value" : "2.16.840.1.113883.18.2",
-                        "type" : "Literal"
-                     }
-                  }, {
-                     "value" : {
-                        "valueType" : "{urn:hl7-org:elm:r1}String",
-                        "value" : "HL7V2.6",
-                        "type" : "Literal"
-                     }
-                  } ]
-               },
-               "valueset" : {
-                  "name" : "Female"
-               }
+               "type" : "Null"
             }
          } ]
       }


more on fingering selection

more on fingering selection

--- intervals.coffee
+++ intervals.coffee
@@ -94,7 +94,6 @@
     _.chain(fs).pluck('note_number').uniq().value().length

   chord_note_count = count_distinct_notes(fingerings)
-  # console.info note_count

   has_all_notes = (fs) ->
     return count_distinct_notes(fs) == chord_note_count
@@ -102,35 +101,49 @@
   closed_medial_strings = (fs) ->
     string_frets = (-1 for s in StringNumbers)
     string_frets[f.string - 1] = f.fret for f in fs
-    p = ((if x >= 0 then 'o' else 'x') for x in string_frets).join('')
-    return p.match(/ox+o/)
+    p = ((if x >= 0 then x else 'x') for x in string_frets).join('')
+    return p.match(/\dx+\d/)

   closed_treble_strings = (fs) ->
     string_frets = (-1 for s in StringNumbers)
     string_frets[f.string - 1] = f.fret for f in fs
-    p = ((if x >= 0 then 'o' else 'x') for x in string_frets).join('')
+    p = ((if x >= 0 then x else 'x') for x in string_frets).reverse().join('')
     return p.match(/x$/)

   finger_count = (fs) ->
     (f for f in fs when f.fret).length

-  high_note_count = (fs) -> fs.length
-
-  has_low_fingering = (fs) ->
-    (f for f in fs when f.fret).length
+  high_note_count = (fs) -> -fs.length

   is_first_position = (fs) ->
     _(fs).sortBy((f) -> -f.string)[0].note_number == 0

   cmp = (fn) -> (x...) -> !fn(x...)
+
+  filters = [
+    {name: 'has all chord notes', filter: has_all_notes},
+    {name: 'no muted medial strings', filter: cmp(closed_medial_strings)},
+    {name: 'no muted treble strings', filter: cmp(closed_treble_strings)}
+  ]
+
+  sorts = [
+    finger_count,
+    high_note_count,
+    cmp(is_first_position)
+  ]
+
+  chord_name = "#{NoteNames[root_note % 12]}#{chord.abbr}"
   choices = compute_choices(frets_per_string)
-  choices = (choice for choice in choices when has_all_notes(choice))
-  choices = (choice for choice in choices when not closed_medial_strings(choice))
-  # choices = (choice for choice in choices when not closed_treble_strings(choice))
-  choices = (choice for choice in choices when finger_count(choice) <= 4)
-  choices = _(choices).sortBy(has_low_fingering)
-  choices = _(choices).sortBy(cmp(high_note_count))
-  choices = _(choices).sortBy(cmp(is_first_position))
+  for {name, filter} in filters
+    filtered = (choice for choice in choices when filter(choice))
+    unless filtered.length
+      console.error "#{chord_name}: fatal filter #{name}"
+      filtered = choices
+    choices = filtered
+  for sort in sorts
+    choices = _(choices).sortBy(sort)
+  # for choice in choices
+  #   console.info finger_count(choice)
   return choices

 best_fingering_for = (chord, root_note) ->
@@ -176,6 +189,7 @@
   page cols * cell_width, header_height + rows * cell_height, (ctx) ->
     i = 0
     draw_page (draw_cell) ->
+      return if i >= cols * rows
       ctx.save()
       ctx.translate (i % cols) * cell_width, header_height + Math.floor(i / cols) * cell_height
       draw_cell()
@@ -188,7 +202,7 @@
   draw_book (draw_page) ->
     draw_page()
     ctx.addPage()
-  fs.writeFile BuildDirectory + filename, canvas.toBuffer()
+  fs.writeFile BuildDirectory + filename + ".pdf", canvas.toBuffer()

 #
@@ -391,21 +405,22 @@

 intervals_book = ({by_root}) ->
   if by_root
-    book "Fretboard Intervals by Root.pdf", (page) ->
+    book "Fretboard Intervals by Root", (page) ->
       finger_positions_each (fingering) ->
         page -> intervals_from_position_page fingering
   else
-    book "Fretboard Intervals.pdf", (page) ->
+    book "Fretboard Intervals", (page) ->
       for _, semitones in Intervals
         page -> intervals_page semitones

 chord_fingerings_page = (chord, root_note) ->
   fingerings = fingerings_for(chord, root_note)
   filename "Fingerings"
-  cols = 10
-  rows = 10
-  grid {cols, rows, cell_width: padded_fretboard_width + 10, cell_height: padded_fretboard_height + 5}, (cell) ->
-    for fingering, i in fingerings[...(cols * rows)]
+  grid cols: 10, rows: 10
+  , cell_width: padded_fretboard_width + 10
+  , cell_height: padded_fretboard_height + 5
+  , (cell) ->
+    for fingering, i in fingerings
       cell -> draw_fingerboard(fingering)

 chord_page = (chord, options) ->
@@ -414,25 +429,23 @@
   diagram_gutter = 20
   header_height = 40
   diagram_title_height = 30
-  cols = 3
-  rows = 4
   if draw_diagrams
     diagram_title_height = 35
     diagram_gutter = 10

   pitch_fingers = []
   finger_positions_each (fingering) ->
-    pitch_number = fingering_note_number(fingering) % 12
+    pitch_number = (fingering_note_number(fingering) - 44 + 120) % 12
     pitch_fingers[pitch_number] = fingering

   colors = ['red', 'blue', 'green', 'orange']
   other_colors = ['rgba(255,0,0 ,.1)', 'rgba(0,0,255, 0.1)', 'rgba(0,255,0, 0.1)', 'rgba(255,0,255, 0.1)']

-  grid {cols, rows
+  grid cols: 3, rows: 4
   , cell_width: padded_fretboard_width + diagram_gutter
   , cell_height: padded_fretboard_height + diagram_gutter
   , header_height: diagram_title_height
-  }, (cell) ->
+  , (cell) ->
     ctx.font = '20px Impact'
     ctx.fillStyle = 'rgb(128, 128, 128)'
     ctx.fillText "#{chord.name} Chords", diagram_gutter / 2, header_height / 2
@@ -440,23 +453,23 @@
     for ix in [0...12]
       pitch_number = (ix * 5 + 7) % 12
       root_fingering = pitch_fingers[pitch_number]
+      chord_name = "#{NoteNames[pitch_number]}#{chord.abbr}"
       cell ->
-        ctx.font = '20px Impact'
-        ctx.font = '5pt Times' if draw_diagrams
-        ctx.fillStyle = 'rgb(10,20,30)'
-        ctx.fillText "#{NoteNames[pitch_number]}#{chord.abbr}", h_gutter, -3
         fingerings = []
         for semitones, note_number in chord.offsets
           for {string, fret} in intervals_from(root_fingering, semitones)
             fingerings.push {string, fret, note_number}
         fingerings = best_fingering_for(chord, pitch_number) if best_fingering
-        for f in fingerings
-          f.color = colors[f.note_number]
+        f.color = colors[f.note_number] for f in fingerings
+        ctx.font = '20px Impact'
+        ctx.font = '5pt Times' if draw_diagrams
+        ctx.fillStyle = 'rgb(10,20,30)'
+        ctx.fillText chord_name, h_gutter, -3
         draw_fingerboard fingerings

 chord_book = (options) ->
   page_count = options.pages
-  book "Combined Fretboard Chords.pdf", (page) ->
+  book "Combined Fretboard Chords", (page) ->
     for chord, i in Chords
       page -> chord_page chord, options
       break if page_count and i + 1 >= page_count
@@ -465,4 +478,4 @@
 # intervals_book by_root: true
 # intervals_book by_root: false
 chord_book best_fingering: 0, pages: 0
-# chord_fingerings_page Chords[0], 44
+# chord_fingerings_page Chords[0], 44 + 3


eliminated usage of "unless...else" syntax

--- lib/assets/javascripts/firehose/transport.js.coffee
+++ lib/assets/javascripts/firehose/transport.js.coffee
@@ -21,13 +21,11 @@

   # Default error handler
   _error: (event) =>
-    unless @_succeeded
-      # Fail peremantly if the error happens on the first connection.
-      @config.failed(this)
-    else
+    if @_succeeded
       # Lets try to connect again with delay
       @config.disconnected()
       @connect(@_retryDelay)
+    else @config.failed @

   # Default connection established handler
   _open: (event) =>


fixing autofocus for fullpage refresh

fixing autofocus for fullpage refresh

--- lib/assets/javascripts/turbograft/turbolinks.coffee
+++ lib/assets/javascripts/turbograft/turbolinks.coffee
@@ -103,7 +103,6 @@

   @loadPage: (url, xhr, partialReplace = false, onLoadFunction = (->), replaceContents = [], replaceAllExcept = []) ->
     triggerEvent 'page:receive'
-

     if doc = processResponse(xhr, partialReplace)
       reflectNewUrl url
@@ -133,6 +132,7 @@
       triggerEvent 'page:before-replace'
       document.documentElement.replaceChild body, document.body
       CSRFToken.update csrfToken if csrfToken?
+      setAutofocusElement()
       executeScriptTags() if runScripts
       currentState = window.history.state
       triggerEvent 'page:change'
@@ -154,6 +154,11 @@
       matchingNodes.push(node)

     return matchingNodes
+
+  setAutofocusElement = ->
+    autofocusElement = (list = document.querySelectorAll 'input[autofocus], textarea[autofocus]')[list.length - 1]
+    if autofocusElement and document.activeElement isnt autofocusElement
+      autofocusElement.focus()

   deleteRefreshNeverNodes = (body) ->
     for node in body.querySelectorAll('[refresh-never]')


Fixed #55

Fixed #55

--- lib/views/cloud-variables-and-functions-view.coffee
+++ lib/views/cloud-variables-and-functions-view.coffee
@@ -47,6 +47,7 @@

     @client = null
     @watchers = {}
+    @variablePromises = {}

     @listVariables()
     @listFunctions()
@@ -124,9 +125,19 @@
     cell = @find('#spark-dev-cloud-variables [data-id=' + variableName + '] td:eq(2)')
     cell.addClass 'loading'
     cell.text ''
+    promise = @variablePromises[variableName]
+    if !!promise
+      promise._handler.resolve()
     promise = spark.getVariable SettingsHelper.get('current_core'), variableName
+    @variablePromises[variableName] = promise
     promise.done (e) =>
+      if !e
+        dfd.resolve null
+        return
+
+      delete @variablePromises[variableName]
       cell.removeClass()
+
       if !!e.ok
         cell.addClass 'icon icon-issue-opened text-error'
         dfd.reject()
@@ -134,6 +145,7 @@
         cell.text e.result
         dfd.resolve e.result
     , (e) =>
+      delete @variablePromises[variableName]
       cell.removeClass()
       cell.addClass 'icon icon-issue-opened text-error'
       dfd.reject()


fix(edit skill): Add edit skill back to skill view.

--- src/app/skill/skill.coffee
+++ src/app/skill/skill.coffee
@@ -40,7 +40,14 @@
     url: "/:skillTitle/view"
     controller: ($scope, $stateParams)->
       $scope.slug = $stateParams.skillTitle
-    template: "<skill slug=\"slug\">"
+    template: """
+    <a ng-href="#/skill/{{slug}}/edit"
+      ng-show-auth="login" role="admin"
+      yc-key="e">
+      Edit
+    </a>
+    <skill slug="slug">
+    """
     data:
       pageTitle: "Skill"


Semicolons in your coffee.

Semicolons in your coffee.

--- application/chrome/content/wesabe/download/Controller.coffee
+++ application/chrome/content/wesabe/download/Controller.coffee
@@ -67,7 +67,7 @@

           while (index = @request.indexOf("\n")) != -1
             requestText = @request.substring(0, index)
-            @request = @request.substring(index+1);
+            @request = @request.substring(index+1)

             request      = wesabe.lang.json.parse(requestText)
             response     = @dispatch(request)
@@ -122,7 +122,7 @@

       @job.start()
     catch e
-      wesabe.error('job.start: ', e);
+      wesabe.error('job.start: ', e)

     return {response: {status: 'ok'}}


set the working directory for the kernel to wherever the file is

--- lib/kernel.coffee
+++ lib/kernel.coffee
@@ -36,7 +36,14 @@

         console.log "launching kernel:", commandString
         @connect()
-        @kernelProcess = child_process.exec(commandString)
+        # projectPath = atom.project.getPaths()[0]
+        projectPath = path.dirname(atom.workspace.getActiveTextEditor().getPath())
+        if projectPath?
+            @kernelProcess = child_process.exec(commandString, {
+                    cwd: projectPath
+                })
+        else
+            @kernelProcess = child_process.exec(commandString)

         # exec commandString, (error, stdout, stderr) ->
         #     console.log 'stdout: ', stdout


Fixes minor bugs in component names

Fixes minor bugs in component names

--- lib/add-dialog.coffee
+++ lib/add-dialog.coffee
@@ -36,8 +36,8 @@
       'component-template/index.txt' ]
       .map((f) =>
         fs.readFileSync(path.join(__dirname, f), 'utf8')
-        .replace(/##COMPONENT_NAME##/g, componentName)
-        .replace(/##COMPONENT_NAME_CAMEL##/g, _.camelCase(componentNameCamel))
+        .replace(/##COMPONENT_NAME##/g, componentNameCap)
+        .replace(/##COMPONENT_NAME_CAMEL##/g, componentNameCamel)
     )
     return unless componentPath


add layout overlay to atom-workspace

add layout overlay to atom-workspace

--- lib/layout.coffee
+++ lib/layout.coffee
@@ -4,7 +4,7 @@

   activate: ->
     @view = document.createElement 'div'
-    document.body.appendChild @view
+    atom.views.getView(atom.workspace).appendChild @view
     @view.classList.add 'tabs-layout-overlay'

   deactivate: ->


editor: further tweaking some EDIT_COMMANDS: adding newlines, LaTeX instead of TeX (I think this is better?), etc.

--- salvus/page/misc_page.coffee
+++ salvus/page/misc_page.coffee
@@ -696,11 +696,11 @@
         tex :
             bold :
                 wrap :
-                    left  : '{\\bf '
+                    left  : '\\textbf{'
                     right : '}'
             italic :
                 wrap :
-                    left  : '{\\em '
+                    left  : '\\textit{'
                     right : '}'
             underline :
                 wrap :
@@ -756,7 +756,8 @@
                     right : ''
             horizontalRule:
                 wrap:
-                    left  : "\n\noindent\makebox[\linewidth]{\rule{\paperwidth}{0.4pt}}\n"
+                    left  : "\\hrulefill"
+                    #left  : "\n\\noindent\\makebox[\\linewidth]{\\rule{\\paperwidth}{0.4pt}}\n"
                     right : ""

         md :
@@ -787,30 +788,30 @@
             format_heading_1 :  # todo -- define via for loop below
                 strip : ['format_heading_2','format_heading_3','format_heading_4']
                 wrap :
-                    left  : "#"
+                    left  : "\n# "
                     right : ""
             format_heading_2 :
                 strip : ['format_heading_1','format_heading_3','format_heading_4']
                 wrap :
-                    left  : "##"
+                    left  : "\n## "
                     right : ""
             format_heading_3 :
                 strip : ['format_heading_1','format_heading_2','format_heading_4']
                 wrap :
-                    left  : "###"
+                    left  : "\n### "
                     right : ""
             format_heading_4 :
                 strip : ['format_heading_1','format_heading_2','format_heading_3']
                 wrap :
-                    left  : "####"
+                    left  : "\n#### "
                     right : ""
             format_code :  # TODO: I think indentation is probably nicer?  on single line ` is nicer.
                 wrap :
-                    left  : '```'
-                    right : '```'
+                    left  : '\n```'
+                    right : '\n```\n'
             indent :
                 wrap :
-                    left  : "> "
+                    left  : "\n> "
                     right : ""
             horizontalRule:
                 wrap:


testing: misc.ActivityLog ("best effort")

--- salvus/test/misc-test.coffee
+++ salvus/test/misc-test.coffee
@@ -224,7 +224,7 @@
         cnt(Y, X).should.be.exactly 6
     it "counts special characters", ->
         cnt("we ¢ount ¢oins", "¢").should.eql 2
-    it "and returns zero if nothing has been found", ->
+    it "returns zero if nothing has been found", ->
         cnt("'", '"').should.eql 0

 describe "min_object of target and upper_bound", ->
@@ -329,7 +329,7 @@
         dstr = '"2015-01-02T03:04:05+00:00"'
         exp = new Date(2015, 0, 2, 3, 2, 5)
         #expect(from_json(dstr)).toBeA(Date).toEqual exp
-    it "and throws an error for garbage", ->
+    it "throws an error for garbage", ->
         (-> from_json '{"x": ]').should.throw /^Unexpected token/

 describe "to_safe_str", ->
@@ -376,7 +376,7 @@
                 nested = child
             child.ref = parent
             tjc(nested).should.match((x) -> x.indexOf('{"ref":{"ref":"[Circular]"}}') > 0)
-        it "and [Unknown] for even deeper nested non-circular references", ->
+        it "detects [Unknown] for even deeper nested non-circular references", ->
             nested = parent = {}
             for i in [1..29] # only change to above is 29 here
                 child = {}
@@ -410,7 +410,7 @@
         exp   = [1, 2, "x", 8, "y", "x", "zzz", [1, 2], "x"]
         (-> rm(input, "z")).should.throw /item not in array/
         input.should.eql exp
-    it "or with an empty argument", ->
+    it "works with an empty argument", ->
         (-> rm([], undefined)).should.throw /item not in array/

 describe "to_iso", ->
@@ -426,7 +426,7 @@
     it "detects empty objects", ->
         ie({}).should.be.ok()
         ie([]).should.be.ok()
-    it "and nothing else", ->
+    it "doesn't detect anything else", ->
         #ie("x").should.not.be.ok()
         ie({a:5}).should.not.be.ok()
         ie(b:undefined).should.not.be.ok()
@@ -447,7 +447,7 @@
     it "correctly returns the keys of an object", ->
         k({a:5, xyz:'10'}).should.be.eql ['a', 'xyz']
         k({xyz:'10', a:5}).should.be.eql ['xyz', 'a']
-    it "and doesn't choke on empty objects", ->
+    it "doesn't choke on empty objects", ->
         k([]).should.be.eql []
         k({}).should.be.eql []

@@ -486,7 +486,7 @@
         @ssc(@string, @substr1, true).should.be.exactly 2
         @ssc(@string, @substr2, true).should.be.exactly 3
         @ssc(@string, @substr3, true).should.be.exactly 3
-    it "also counts empty strings", =>
+    it "counts empty strings", =>
         @ssc(@string, "").should.be.exactly 47

@@ -625,7 +625,7 @@
         t(long).should.endWith("...").and.has.length 1024
     it "and handles empty strings", ->
         t("").should.be.eql ""
-    it "and undefined", ->
+    it "handles missing argument", ->
         should(t()).be.eql undefined

 describe "trunc_left", ->
@@ -640,9 +640,9 @@
     it "defaults to lenght 1024", ->
         long = ("x" for [1..10000]).join("")
         tl(long).should.startWith("...").and.has.length 1024
-    it "and handles empty strings", ->
+    it "handles empty strings", ->
         tl("").should.be.eql ""
-    it "and undefined", ->
+    it "handles missing argument", ->
         should(tl()).be.eql undefined

 describe "git_author", ->
@@ -944,6 +944,105 @@
         # that this is false
         @objspy.should.have.properties __call_lock: true

+
+describe "timestamp_cmp", ->
+    tcmp = misc.timestamp_cmp
+    a = timestamp: new Date("2015-01-01")
+    b = timestamp: new Date("2015-01-02")
+
+    it "correctly compares timestamps", ->
+        tcmp(a, b).should.eql 1
+        tcmp(b, a).should.eql -1
+        tcmp(a, a).should.eql 0
+
+    it "handles missing timestamps gracefully", ->
+        tcmp(a, {}).should.eql -1
+        tcmp({}, b).should.eql 1
+
+describe "ActivityLog", =>
+    beforeEach =>
+        # e1 and e2 are deliberately on the same file
+        @e1 =
+            id: "1234"
+            timestamp: new Date("2015-01-01T12:34:55")
+            project_id: "c26db83a-7fa2-44a4-832b-579c18fac65f"
+            path: "foo/bar.baz"
+
+        @e2 =
+            id: "2345"
+            timestamp: new Date("2015-01-02T12:34:56")
+            project_id: "c26db83a-7fa2-44a4-832b-579c18fac65f"
+            path: "foo/bar.baz"
+
+        @e3 =
+            id: "3456"
+            timestamp: new Date("2015-01-01T12:34:55")
+            project_id: "c26db83a-7fa2-44a4-832b-579c18fac65f"
+            path: "x/y.z"
+            action: 'c26db83a-7fa2-44a4-832b-579c18fac65f/foo/bar.baz'
+            seen_by: "123456789"
+            read_by: "123456789"
+
+        @al = misc.activity_log
+                    events: [@e1, @e2, @e3]
+                    account_id: "123456789"
+                    notifications: {}
+
+    describe "constructor", =>
+        it "works correctly", =>
+            @al.should.have.properties
+                notifications:
+                    'c26db83a-7fa2-44a4-832b-579c18fac65f/foo/bar.baz':
+                        id: '2345'
+                        timestamp: new Date("2015-01-02T12:34:56")
+                    'c26db83a-7fa2-44a4-832b-579c18fac65f/x/y.z':
+                        id: '3456'
+                        timestamp: new Date("2015-01-01T12:34:55")
+                        "c26db83a-7fa2-44a4-832b-579c18fac65f/foo/bar.baz":
+                            "undefined": new Date("2015-01-01T12:34:55")
+                        read: new Date("2015-01-01T12:34:55")
+                        seen: new Date("2015-01-01T12:34:55")
+                account_id: "123456789"
+
+    describe "obj", =>
+        it "returns a map with the last notification", =>
+            @al.obj().should.eql
+                notifications:
+                    "c26db83a-7fa2-44a4-832b-579c18fac65f/foo/bar.baz":
+                        id: "2345"
+                        timestamp: new Date("2015-01-02T12:34:56")
+                    "c26db83a-7fa2-44a4-832b-579c18fac65f/x/y.z":
+                        id: "3456"
+                        timestamp: new Date("2015-01-01T12:34:55")
+                        "c26db83a-7fa2-44a4-832b-579c18fac65f/foo/bar.baz":
+                            "undefined": new Date("2015-01-01T12:34:55")
+                        read: new Date("2015-01-01T12:34:55")
+                        seen: new Date("2015-01-01T12:34:55")
+                account_id: "123456789"
+
+    describe "process", =>
+        it "correctly processes additional events", =>
+            @al.process([
+                id: "4567"
+                timestamp: new Date("2015-01-03T12:34:56")
+                project_id: "c26db83a-7fa2-44a4-832b-579c18fac65h"
+                path: "x/y.z"
+            ])
+            @al.notifications.should.eql
+                    "c26db83a-7fa2-44a4-832b-579c18fac65f/foo/bar.baz":
+                        id: "2345"
+                        timestamp: new Date("2015-01-02T12:34:56")
+                    "c26db83a-7fa2-44a4-832b-579c18fac65f/x/y.z":
+                        id: "3456"
+                        timestamp: new Date("2015-01-01T12:34:55")
+                        "c26db83a-7fa2-44a4-832b-579c18fac65f/foo/bar.baz":
+                            "undefined": new Date("2015-01-01T12:34:55")
+                        read: new Date("2015-01-01T12:34:55")
+                        seen: new Date("2015-01-01T12:34:55")
+                    "c26db83a-7fa2-44a4-832b-579c18fac65h/x/y.z":
+                        id: "4567"
+                        timestamp: new Date("2015-01-03T12:34:56")
+
 describe "encode_path", ->
     e = misc.encode_path
     it "escapes # and ?", ->
@@ -960,6 +1059,7 @@
         r("foo").should.eql "foo"
     it "removes multiple comments in one string", ->
         r("/* */foo/*remove*/bar").should.eql "foobar"
+

 describe "capitalize", ->
     c = misc.capitalize
@@ -985,11 +1085,11 @@
         """
         pm(s).should.be.eql([[ 10, 19 ], [ 30, 41 ], [ 42, 75 ]])
              .and.matchEach (x) -> s.slice(x[0], x[1]).should.containEql "formula"
-    it "also detects brackets", ->
+    it "detects brackets", ->
         s = "\\(foo\\) and \\[foo\\]"
         pm(s).should.eql([[0, 7], [12, 19]])
              .and.matchEach (x) -> s.slice(x[0]+2, x[1]-2).should.eql "foo"
-    it "and works for other environments", ->
+    it "works for other environments", ->
         pm("\\begin{equation}foobar\\end{equation}").should.eql [[0, 36]]
         pm("\\begin{equation*}foobar\\end{equation*}").should.eql [[0, 38]]
         pm('\\begin{align}foobar\\end{align}').should.eql [[0, 30]]


Create inactiveStates list constant

Create inactiveStates list constant

--- SingularityUI/app/constants.coffee
+++ SingularityUI/app/constants.coffee
@@ -65,4 +65,9 @@
         FAILED: 'Failed'
         CANCELED: 'Canceled'

+constants.inactiveTaskStates = []
+for state, stateObj of constants.taskStates
+    if stateObj.isActive is false
+        constants.inactiveTaskStates.push state
+
 module.exports = constants


Fix for escape handling

Fix for escape handling

--- client/Social/Activity/views/comments/inputeditwidget.coffee
+++ client/Social/Activity/views/comments/inputeditwidget.coffee
@@ -15,7 +15,7 @@

     super

-    @input.on 'Escape', @bound 'cancel'
+    @input.on 'EscapePerformed', @bound 'cancel'

   reset: ->


landing: add oauth handler to register form

--- client/landing/site.landing/coffee/login/registerform.coffee
+++ client/landing/site.landing/coffee/login/registerform.coffee
@@ -230,6 +230,43 @@
       return no

+  handleOauthData: (oauthData) ->
+
+    @oauthData = oauthData
+    { input }  = @email
+
+    { username, firstName, lastName } = oauthData
+
+    console.log {oauthData, input}
+
+    if oauthData.email
+      input.setValue oauthData.email
+      @email.placeholder.setClass 'out'
+      @emailIsAvailable = yes
+    else
+      modal = new KDModalView
+        cssClass        : "fbauth-failed-modal"
+        title           : "OAuth authentication failed"
+        content         : "Sorry, but we could not get a valid email address from your OAuth provider. Please select another method to register."
+        buttons         :
+          OK            :
+            cssClass    : "solid green medium"
+            title       : "OK"
+            callback    : -> modal.destroy()
+
+
+    @once 'gravatarInfoFetched', (gravatar) =>
+      # oath username has more priority over gravatar username
+      gravatar.preferredUsername = username  if username
+      gravatar.name =
+        givenName  : firstName
+        familyName : lastName
+
+      input.validate()
+
+    @fetchGravatarInfo input.getValue()
+
+
   pistachio:->
     """
     <section class='main-part'>


removes debug logging

removes debug logging

--- lib/Model.coffee
+++ lib/Model.coffee
@@ -283,7 +283,6 @@

     @createIndexes: (cb) ->
       @r().indexList().run db.getConn(), (err, existed) =>
-        console.log err, existed

         indexes = ([name, val] for name, val of @indexes)
         createIndex = ([name, val], cb) =>


Disable telemetry consent

Disable telemetry consent

Hey, @atom blocks me, I block them. Simple.

--- config.cson
+++ config.cson
@@ -37,6 +37,7 @@
 			"welcome"
 		]
 		openEmptyEditorOnStart: false
+		telemetryConsent: "no"
 		themes: [
 			"atom-light-ui"
 			"biro-syntax"


bugfix

bugfix

--- proxy.coffee
+++ proxy.coffee
@@ -3,6 +3,7 @@
 randomstring = require 'randomstring'
 bcrypt = require 'bcrypt'
 fs = require 'fs'
+util = require 'util'
 qs = require 'querystring'
 passport = require 'passport'
 LocalStrategy = require('passport-local').Strategy


restructured loofah and improved regex

restructured loofah and improved regex

--- lib/loofah.coffee
+++ lib/loofah.coffee
@@ -4,13 +4,23 @@
 class Scrubbers
   bk_def = ['password', 'user', 'email', 'api', 'secret']
   ue_def = ['client_id', 'client_secret', 'refresh_token']
-  pt_def = ['user', 'password', 'email']
+  pt_def = ['user', 'username', 'password', 'email']

   @default: ->
     bk = Scrubbers.bad_keys()
     ue = Scrubbers.url_encode()
     pt = Scrubbers.plain_text()
     _.compose(pt, ue, bk)
+
+  @bad_keys: (b_keys) ->
+    b_keys = bk_def if not b_keys?[0]? or not _.isArray b_keys
+    return (object) ->
+      return object if not _.isObject object
+      return obj = _.deepMapValues object, (val, key) ->
+        _.each b_keys, (b_key) ->
+          b_key = new RegExp "(\\.|^)#{b_key}(\\.|$)", 'i' if not _.isRegExp b_key
+          val = if (b_key.test key) then '[REDACTED]' else val
+        val

   @bad_vals: (substrings) ->
     return (object) =>
@@ -25,16 +35,6 @@
       string = string.replace substring, '[REDACTED]'
     string

-  @bad_keys: (b_keys) ->
-    b_keys = bk_def if not b_keys?[0]? or not _.isArray b_keys
-    return (object) ->
-      return object if not _.isObject object
-      return obj = _.deepMapValues object, (val, key) ->
-        _.each b_keys, (b_key) ->
-          b_key = new RegExp b_key, 'i' if not _.isRegExp b_key
-          val = if (b_key.test key) then '[REDACTED]' else val
-        val
-
   @url_encode: (query_params) ->
     query_params = ue_def if not query_params?[0]? or not _.isArray query_params
     return (object) =>
@@ -46,11 +46,10 @@
     return string if not _.isString string
     val = @_splitter string, [/[=.&?]/, /[^=.&?]/]
     _.each query_params, (qparam) ->
-      qparam = new RegExp "#{qparam}", 'i' if not _.isRegExp qparam
+      qparam = new RegExp "(^|[=.&?])#{qparam}", 'i' if not _.isRegExp qparam
       _.each val, (v, i) ->
         val[i + 2] = "[REDACTED]" if val[i + 2]? and val[i + 1] is '=' and qparam.test v
     val.join('')
-

   @plain_text: (keywords) ->
     keywords = pt_def if not keywords?[0]? or not _.isArray keywords
@@ -63,7 +62,7 @@
     return string if not _.isString string
     val = @_splitter string , [/[^=:\s]/, /[\s=:]/]
     _.each keywords, (keyword) ->
-      keyword = new RegExp "#{keyword}", 'i' if not _.isRegExp keyword
+      keyword = new RegExp "^#{keyword}$", 'i' if not _.isRegExp keyword
       _.each val, (v, i) ->
         val[i + 2] = '[REDACTED]' if val[i + 1] isnt '=' and val[i + 2]? and keyword.test v
     val.join('')


Fixed believes => disbelieves/doubts

Fixed believes => disbelieves/doubts

--- core/app/backbone/views/activities/activity_item_view.coffee
+++ core/app/backbone/views/activities/activity_item_view.coffee
@@ -32,9 +32,9 @@
     switch @model.get('action')
       when "believes"
         Factlink.Global.t.fact_believe_past_singular_action_about
-      when "believes"
+      when "disbelieves"
         Factlink.Global.t.fact_disbelieve_past_singular_action_about
-      when "believes"
+      when "doubts"
         Factlink.Global.t.fact_doubt_past_singular_action_about

 class FollowedUserView extends ActivityItemView


No need for layout here

No need for layout here

--- core/app/backbone/views/share/preview_share_fact_view.coffee
+++ core/app/backbone/views/share/preview_share_fact_view.coffee
@@ -1,4 +1,4 @@
-class window.PreviewShareFactView extends Backbone.Marionette.Layout
+class window.PreviewShareFactView extends Backbone.Marionette.ItemView
   className: 'preview-share-fact'

   template: 'share/preview_share_fact'


Fixed style binding tests.

Fixed style binding tests.

--- tests/batman/view/style_binding_test.coffee
+++ tests/batman/view/style_binding_test.coffee
@@ -1,17 +1,18 @@
 helpers = if typeof require is 'undefined' then window.viewHelpers else require './view_helper'

-QUnit.module 'Batman.View style bindings',
+QUnit.module 'Batman.View style bindings'
+
 asyncTest 'data-bind-style should bind to a string', 4, ->
   source = '<input type="text" data-bind-style="string"></input>'
-  context = Batman
+  context =
     string: 'backgroundColor:blue; color:green;'

-  helpers.render source, context, (node) ->
+  helpers.render source, context, (node, view) ->
     node = node[0]
     equal node.style['backgroundColor'], 'blue'
     equal node.style['color'], 'green'

-    context.set 'string', 'color: green'
+    view.set 'string', 'color: green'
     equal node.style['backgroundColor'], ''
     equal node.style['color'], 'green'

@@ -19,16 +20,17 @@

 asyncTest 'data-bind-style should bind to a vanilla object', 4, ->
   source = '<input type="text" data-bind-style="object"></input>'
-  context = Batman
+  context =
     object:
       backgroundColor: 'blue'
       color: 'green'

-  helpers.render source, context, (node) ->
+  helpers.render source, context, (node, view) ->
     node = node[0]
     equal node.style['backgroundColor'], 'blue'
     equal node.style['color'], 'green'
-    context.set 'object', {color: 'red'}
+
+    view.set('object', {color: 'red'})
     equal node.style['backgroundColor'], ''
     equal node.style['color'], 'red'

@@ -36,25 +38,25 @@

 asyncTest 'data-bind-style should bind to a Batman object', ->
   source = '<input type="text" data-bind-style="object"></input>'
-  context = Batman
+  context =
     object: new Batman.Object
       'backgroundColor': 'blue'
       color: 'green'

-  helpers.render source, context, (node) ->
+  helpers.render source, context, (node, view) ->
     node = node[0]
     equal node.style['backgroundColor'], 'blue'
     equal node.style['color'], 'green'

-    context.set 'object.color', 'blue'
+    view.set('object.color', 'blue')
     equal node.style['color'], 'blue'
     equal node.style['backgroundColor'], 'blue'

-    context.unset 'object.color'
+    view.unset('object.color')
     equal node.style['color'], ''
     equal node.style['backgroundColor'], 'blue'

-    context.set 'object', new Batman.Object color: 'yellow'
+    view.set('object', new Batman.Object color: 'yellow')
     equal node.style['color'], 'yellow'
     equal node.style['backgroundColor'], ''

@@ -65,13 +67,13 @@
   hash = new Batman.Hash
     'backgroundColor': 'blue'
     color: 'green'
-  context = Batman hash: hash
-  helpers.render source, context, (node) ->
+
+  helpers.render source, {hash}, (node, view) ->
     node = node[0]
     equal node.style['backgroundColor'], 'blue'
     equal node.style['color'], 'green'

-    context.set 'hash', new Batman.Hash color: 'red'
+    view.set('hash', new Batman.Hash color: 'red')
     equal node.style['backgroundColor'], ''
     equal node.style['color'], 'red'

@@ -83,15 +85,15 @@

 asyncTest 'data-bind-style should bind dash-separated CSS keys to camelized ones', 4, ->
   source = '<input type="text" data-bind-style="string"></input>'
-  context = Batman
+  context =
     string: 'background-color:blue; color:green;'

-  helpers.render source, context, (node) ->
+  helpers.render source, context, (node, view) ->
     node = node[0]
     equal node.style['backgroundColor'], 'blue'
     equal node.style['color'], 'green'

-    context.set 'string', 'color: green'
+    view.set('string', 'color: green')
     equal node.style['backgroundColor'], ''
     equal node.style['color'], 'green'

@@ -99,7 +101,7 @@

 asyncTest 'data-bind-style should correctly work for style with absolute URL', 1, ->
   source = '<input type="text" data-bind-style="string"></input>'
-  context = Batman
+  context =
     string: 'background-image: url("http://batmanjs.org/images/logo.png");'

   helpers.render source, context, (node) ->


Update bid.coffee

Update bid.coffee

--- tests/jasmine/client/integration/bid.coffee
+++ tests/jasmine/client/integration/bid.coffee
@@ -1,6 +1,6 @@
 describe "When a bid is made", ->
   beforeEach (done) ->
-    Meteor.loginWithPassword "admin@fantanarchist.com", "adidas85", (err) ->
+    Meteor.loginWithPassword "admin@fantanarchist.com", "123456", (err) ->
       $(".roles").val("G")
       $(".draw").click()
       Meteor.logout ->
@@ -14,4 +14,4 @@
       $(".offer").click()
       expect($(".cost").text()).toBe "8"
       #Meteor.logout ->
-      #  done()+      #  done()


:art: isVisualMode can take specificity of submode

--- lib/mode-manager.coffee
+++ lib/mode-manager.coffee
@@ -16,8 +16,11 @@
   isOperatorPendingMode: ->
     @mode is 'operator-pending'

-  isVisualMode: ->
-    @mode is 'visual'
+  isVisualMode: (submode=null) ->
+    if submode
+      @mode is 'visual' and @submode is submode
+    else
+      @mode is 'visual'

   setMode: (@mode, @submode=null) ->
     for mode in ['normal', 'insert', 'visual', 'operator-pending']


Fixed background screen if sort order is used.

--- app/assets/javascripts/app/controllers/agent_ticket_history.js.coffee
+++ app/assets/javascripts/app/controllers/agent_ticket_history.js.coffee
@@ -55,7 +55,8 @@
     # show frontend times
     @delay( @frontendTimeUpdate, 200 )

-  sortorder: ->
+  sortorder: (e) ->
+    e.preventDefault()
     isSorted = @el.find('.sorted')
     @log 'is sorted?', isSorted
     if isSorted.length


enforce minimum mark height

enforce minimum mark height

--- app/assets/javascripts/components/mark/tools/text-row-tool/index.cjsx
+++ app/assets/javascripts/components/mark/tools/text-row-tool/index.cjsx
@@ -39,6 +39,16 @@
     offset = @props.getEventOffset e
     switch whichOne
       when 'upper'
+        # enforce bounds
+        if offset.y < 0
+          offset.y = 0
+          return
+
+        if mark.yLower - offset.y < 100
+          mark.yUpper = Math.round( -100 + mark.yLower )
+          # @setState mark: mark
+          return
+
         dy = mark.yUpper - offset.y
         yUpper_p = offset.y
         markHeight_p = mark.yLower - mark.yUpper + dy
@@ -47,6 +57,17 @@
         mark.markHeight = markHeight_p
         mark.y = y_p
       when 'lower'
+
+        # enforce bounds
+        if offset.y > @state.imageHeight
+          offset.y = @state.imageHeight
+          return
+
+        if offset.y - mark.yUpper < 100
+          mark.yLower = Math.round( 100 + mark.yUpper )
+          # @setState mark: mark
+          return
+
         dy = offset.y - mark.yLower
         yLower_p = offset.y
         markHeight_p = mark.yLower - mark.yUpper + dy


fix typo

fix typo

--- lib/motion.coffee
+++ lib/motion.coffee
@@ -259,7 +259,7 @@
       if point.row in [0, getVimLastScreenRow(@editor)]
         true
       else
-        # If one of above/blow row is not stoppable, it's Edge!
+        # If one of above/below row is not stoppable, it's Edge!
         above = point.translate([-1, 0])
         below = point.translate([+1, 0])
         (not @isStoppablePoint(above)) or (not @isStoppablePoint(below))


do not track private channel views

do not track private channel views

--- components/layout/client.coffee
+++ components/layout/client.coffee
@@ -101,7 +101,7 @@
   # Initialize analytics & track page view.
   analytics ga: ga
   analytics.registerCurrentUser()
-  analytics.trackPageview()
+  analytics.trackPageview() unless sd.CHANNEL and sd.CHANNEL.status is 'private'

   # Log a visit once per session
   unless Cookies.get('active_session')?


just adding a commit so that i can try to redeploy

--- config/main.kontrol-prod.coffee
+++ config/main.kontrol-prod.coffee
@@ -1,6 +1,7 @@
 fs = require 'fs'
 nodePath = require 'path'
 deepFreeze = require 'koding-deep-freeze'
+

 version = (fs.readFileSync nodePath.join(__dirname, '../VERSION'), 'utf-8').trim()
 projectRoot = nodePath.join __dirname, '..'


make s3 configurable via env vars

make s3 configurable via env vars

--- config/settings.defaults.coffee
+++ config/settings.defaults.coffee
@@ -1,6 +1,6 @@
 Path = require "path"

-module.exports =
+settings =
 	internal:
 		filestore:
 			port: 3009
@@ -11,22 +11,26 @@
 		# Choices are
 		# s3 - Amazon S3
 		# fs - local filesystem
-		backend: "fs"
-		stores:
-		  	# where to store user and template binary files
-			#
-			# For Amazon S3 this is the bucket name to store binary files in.
-			#
-			# For local filesystem this is the directory to store the files in.
-			# Must contain full path, e.g. "/var/lib/sharelatex/data".
-			# This path must exist, not be tmpfs and be writable to by the user sharelatex is run as.
-			user_files: Path.resolve(__dirname + "/../user_files")
-			public_files: Path.resolve(__dirname + "/../public_files")
-			template_files: Path.resolve(__dirname + "/../template_files")
-		# if you are using S3, then fill in your S3 details below
-		# s3:
-		# 	key: ""
-		# 	secret: ""
+		if process.env['AWS_KEY']?
+			backend: "s3"
+			s3:
+				key: process.env['AWS_KEY']
+				secret: process.env['AWS_SECRET']
+			stores:
+				user_files: process.env['AWS_S3_USER_FILES_BUCKET_NAME']
+				template_files: process.env['AWS_S3_TEMPLATE_FILES_BUCKET_NAME']
+				public_files: process.env['AWS_S3_PUBLIC_FILES_BUCKET_NAME']
+		else
+			backend: "fs"
+			stores:
+				#
+				# For local filesystem this is the directory to store the files in.
+				# Must contain full path, e.g. "/var/lib/sharelatex/data".
+				# This path must exist, not be tmpfs and be writable to by the user sharelatex is run as.
+				user_files: Path.resolve(__dirname + "/../user_files")
+				public_files: Path.resolve(__dirname + "/../public_files")
+				template_files: Path.resolve(__dirname + "/../template_files")
+

 	path:
 		uploadFolder: Path.resolve(__dirname + "/../uploads")
@@ -35,9 +39,15 @@
 		# Any commands to wrap the convert utility in, for example ["nice"], or ["firejail", "--profile=/etc/firejail/convert.profile"]
 		convertCommandPrefix: []

-	# Filestore health check
-	# ----------------------
-	# Project and file details to check in persistor when calling /health_check
-	# health_check:
-	# 	project_id: ""
-	# 	file_id: ""
+
+# Filestore health check
+# ----------------------
+# Project and file details to check in persistor when calling /health_check
+if process.env['HEALTH_CHECK_PROJECT_ID']? and process.env['HEALTH_CHECK_FILE_ID']?
+	settings.health_check =
+		project_id: process.env['HEALTH_CHECK_PROJECT_ID']
+		file_id: process.env['HEALTH_CHECK_FILE_ID']
+
+module.exports = settings
+
+console.log module.exports


Created app now contains clear 'package.json' with no NPM data

--- lib/tasks/create.coffee
+++ lib/tasks/create.coffee
@@ -6,6 +6,8 @@
 cp = require 'child_process'

 {log} = Neft
+
+PACKAGE_KEYS = ['private', 'name', 'version', 'description', 'config', 'dependencies', 'android']

 module.exports = (dest, options) ->
 	dest ||= './'
@@ -26,4 +28,14 @@
 	else
 		cp.execSync "cd #{dest} ; npm install"

+	# restore 'package.json'
+	projectPackage = JSON.parse fs.readFileSync "#{src}/package.json"
+	packageJson = {}
+	for key in PACKAGE_KEYS
+		packageJson[key] = projectPackage[key]
+	fs.writeFileSync "#{dest}/package.json", JSON.stringify(packageJson, 0, 2)
+
+	# rename .npmignore
+	fs.move "#{dest}/.npmignore", "#{dest}/.gitignore", {}
+
 	log.ok "Project created in '#{dest}'"


removed old homepage/slideshow

removed old homepage/slideshow

--- client/landing/app/Applications/Activity.kdapplication/AppView.coffee
+++ client/landing/app/Applications/Activity.kdapplication/AppView.coffee
@@ -14,7 +14,7 @@
   viewAppended:->

     {entryPoint}      = KD.config
-    HomeKonstructor   = if entryPoint and entryPoint.type isnt 'profile' then GroupHomeView else HomeAppView
+    HomeKonstructor   = if entryPoint and entryPoint.type isnt 'profile' then GroupHomeView else KDCustomHTMLView
     @feedWrapper      = new ActivityListContainer
     @innerNav         = new ActivityInnerNavigation cssClass : 'fl'
     @header           = new HomeKonstructor
@@ -24,15 +24,7 @@

     @mainController.on "AccountChanged", @bound "decorate"
     @mainController.on "JoinedGroup", => @widget.show()
-    @mainController.on "NavigationLinkTitleClick", @bound "navigateHome"

-    # do =>
-    #   bindChangePage = => @on 'scroll', @bound "changePageToActivity"
-    #   if KD.isLoggedIn()
-    #   then do bindChangePage
-    #   else @mainController.once "AccountChanged", bindChangePage
-
-    @on 'scroll', @bound "changePageToActivity"
     @header.bindTransitionEnd()

     @feedWrapper.ready =>
@@ -71,35 +63,35 @@
       @widget.hide()
     @_windowDidResize()

-  changePageToActivity:(event)->
+  # changePageToActivity:(event)->

-    if KD.isLoggedIn()
-      if not @$().hasClass("fixed") and @getScrollTop() > headerHeight - 10
-        {navController} = @mainController.sidebarController.getView()
-        navController.selectItemByName 'Activity'
-        @setClass "fixed"
-        @header.once "transitionend", @header.bound "hide"
-        @header.$().css marginTop : -headerHeight
-        KD.getSingleton('mainViewController').emit "browseRequested"
+  #   if KD.isLoggedIn()
+  #     if not @$().hasClass("fixed") and @getScrollTop() > headerHeight - 10
+  #       {navController} = @mainController.sidebarController.getView()
+  #       navController.selectItemByName 'Activity'
+  #       @setClass "fixed"
+  #       @header.once "transitionend", @header.bound "hide"
+  #       @header.$().css marginTop : -headerHeight
+  #       KD.getSingleton('mainViewController').emit "browseRequested"

-  navigateHome:(itemData)->
+  # navigateHome:(itemData)->

-    switch itemData.pageName
-      when "Home"
-        @header.show()
-        @header._windowDidResize()
-        @scrollTo {duration : 300, top : 0}, =>
-          # if KD.isLoggedIn()
-          @unsetClass "fixed"
-          @header.$().css marginTop : 0
-      when "Activity"
-        if KD.isLoggedIn()
-          @header.once "transitionend", =>
-            @header.hide()
-            @setClass "fixed"
-          @header.$().css marginTop : -headerHeight
-        else
-          @scrollTo {duration : 300, top : @header.getHeight()}
+  #   switch itemData.pageName
+  #     when "Home"
+  #       @header.show()
+  #       @header._windowDidResize()
+  #       @scrollTo {duration : 300, top : 0}, =>
+  #         # if KD.isLoggedIn()
+  #         @unsetClass "fixed"
+  #         @header.$().css marginTop : 0
+  #     when "Activity"
+  #       if KD.isLoggedIn()
+  #         @header.once "transitionend", =>
+  #           @header.hide()
+  #           @setClass "fixed"
+  #         @header.$().css marginTop : -headerHeight
+  #       else
+  #         @scrollTo {duration : 300, top : @header.getHeight()}

   _windowDidResize:->
     return unless @header


Add an icon to a block test.

Add an icon to a block test.

--- lib/ui-demo-view.coffee
+++ lib/ui-demo-view.coffee
@@ -53,7 +53,7 @@
             @label 'You might want to type something here.'
             @div class: 'editor mini editor-colors', 'Something you typed...'
           @div class: 'block', =>
-            @label 'Another field'
+            @label class: 'icon icon-file-directory', 'Another field with an icon'
             @div class: 'editor mini editor-colors', 'Something else you typed...'
           @div class: 'block', =>
             @button class: 'btn', 'Do it'


URL マッチングのタイミング変更

URL マッチングのタイミング変更

pushState する前に、
URL にマッチするメソッドが存在しているかどうかのチェック。

--- src/coffee/kazitori.coffee
+++ src/coffee/kazitori.coffee
@@ -183,6 +183,11 @@
 		 URL にマッチするものがあるかどうかのテストってここでするべきじゃない?
 		###
 		url = @.root + frag.replace(routeStripper, '')
+		if @_matchCheck(@.fragment) is false
+			if @.notFound isnt null
+				@change(@.notFound)
+			@._dispatcher.dispatchEvent(new KazitoriEvent(KazitoriEvent.NOT_FOUND))
+			return
 		if @._hasPushState
 			@.history[ if options.replace then 'replaceState' else 'pushState' ]({}, document.title, url)
 		else if @._wantChangeHash
@@ -388,6 +393,37 @@
 			location.hash = "#" + fragment
 		return

+	#マッチする URL があるかどうか
+	# memo : 20130130
+	# ここでここまでのチェックを実際に行うなら
+	# loadURL, executeHandler 内で同じチェックは行う必要がないはずなので
+	# それぞれのメソッドが簡潔になるようにリファクタする必要がある
+	_matchCheck:(fragment)->
+		matched = []
+		for handler in @.handlers
+			if handler.rule is fragment
+				matched.push true
+
+			if handler.test(fragment)
+				if handler.isVariable and handler.types.length > 0
+					#型チェック用
+					args = handler._extractParams(fragment)
+					argsMatch = []
+					len = args.length
+					i = 0
+
+					while i < len
+						a = args[i]
+						t = handler.types[i]
+						if t is null or @_typeCheck(a,t) is true
+							argsMatch.push true
+						i++
+					if not false in argsMatch
+						matched.push true
+				else
+					matched.push true
+		return if true in matched then true else false
+


Allow newline characters in build/deploy --buildArg values

Change-type: patch
Signed-off-by: Paulo Castro <fd077434a7c3095bfe440741787d02f6a7bab07e@balena.io>

--- lib/utils/docker.coffee
+++ lib/utils/docker.coffee
@@ -130,7 +130,8 @@
 		args = [ args ]
 	buildArgs = {}
 	args.forEach (arg) ->
-		pair = /^([^\s]+?)=(.*)$/.exec(arg)
+		# note: [^] matches any character, including line breaks
+		pair = /^([^\s]+?)=([^]*)$/.exec(arg)
 		if pair?
 			buildArgs[pair[1]] = pair[2] ? ''
 		else


Fixed issue with not flushing spawned process' stdin

--- lib/runner.coffee
+++ lib/runner.coffee
@@ -25,6 +25,7 @@

     if inputString
       @bufferedProcess.process.stdin.write(inputString)
+      @bufferedProcess.process.stdin.end()

     @bufferedProcess.onWillThrowError(@createOnErrorFunc(command))


Incorporate @nixel2007's comments [breaks getProjectPath]

--- lib/runner.coffee
+++ lib/runner.coffee
@@ -51,16 +51,16 @@

     workingDirectoryProvided = cwd? and cwd isnt ''
     if not workingDirectoryProvided
-      try
-        switch atom.config.get('script.cwdBehavior')
-          when 'First project directory'
-            paths = atom.project.getPaths()
-            if paths?.length > 0
+      switch atom.config.get('script.cwdBehavior')
+        when 'First project directory'
+          paths = atom.project.getPaths()
+          if paths?.length > 0
+            try
               cwd = if fs.statSync(paths[0]).isDirectory() then paths[0] else path.join(paths[0], '..')
-          when 'Project directory of the script'
-            cwd = @getProjectPath()
-          when 'Directory of the script'
-            cwd = atom.workspace.getActivePaneItem().buffer.file.getParent().path
+        when 'Project directory of the script'
+          cwd = @getProjectPath()
+        when 'Directory of the script'
+          cwd = atom.workspace.getActivePaneItem()?.buffer?.file?.getParent?().getPath?() or ''
     cwd

   stop: ->
@@ -116,9 +116,11 @@
   getProjectPath: ->
     filePath = atom.workspace.getActiveTextEditor().getPath()
     projectPaths = atom.project.getPaths()
+    curProjectPath = ''
     for projectPath in projectPaths
       if filePath.indexOf(projectPath) > -1
-        if fs.statSync(projectPath).isDirectory()
-          return projectPath
-        else
-          return path.join(projectPath, '..')
+        fs.stat(projectPath, (err, stats) ->
+          if !err
+            curProjectPath = if stats.isDirectory() then projectPath else path.join(projectPath, '..')
+        )
+        return curProjectPath


Improved linter ranges by finding word length of cursor

Signed-off-by: WebFreak001 <b0a51d41aaf8d5df1e850f1ef2ec58fb1c30319f@web.de>

--- lib/dub-linter.coffee
+++ lib/dub-linter.coffee
@@ -4,7 +4,8 @@

 # path(lineNumber): (Deprecation|Warning|Error): <message>
 dubErrorFormat = /(.*?)\((\d+),(\d+)\): (Deprecation|Warning|Error): (.*)/g;
-dubErrorFormatRel = /(.*?)\((\d+),(\d+)\): (.*)/g;
+dubErrorFormatCont = /(.*?)\((\d+),(\d+)\): (.*)/g;
+wordLengthFormat = /^((?:[^\s\w]+)?\w+\b)/; # (optional special characters) + word until word boundary

 module.exports =
   class DubLinter
@@ -21,6 +22,7 @@
         @dubPath = newValue

     lint: (textEditor) =>
+      that = this
       return new Promise (resolve, reject) =>
         output = ""
         args = ["build", "--nodeps", "--combined", "-q"]
@@ -47,25 +49,31 @@
             for line in lines
               match = dubErrorFormat.exec(line)
               if match
+                row = parseInt(match[2]) - 1
+                column = parseInt(match[3]) - 1
+                file = path.join(atom.project.getPaths()[0], match[1])
                 obj.push
                   type: match[4],
                   text: match[5],
-                  filePath: path.join(atom.project.getPaths()[0], match[1]),
+                  filePath: file,
                   range: [
-                    [parseInt(match[2]) - 1, parseInt(match[3]) - 1],
-                    [parseInt(match[2]) - 1, parseInt(match[3]) + 300]
+                    [row, column],
+                    [row, column + that.getWordLength(textEditor, row, column)]
                   ]
               else
                 if line.indexOf("from here") != -1 or line.indexOf("from argument types") != -1
-                  match = dubErrorFormatRel.exec(line)
+                  match = dubErrorFormatCont.exec(line)
                   if match
+                    row = parseInt(match[2]) - 1
+                    column = parseInt(match[3]) - 1
+                    file = path.join(atom.project.getPaths()[0], match[1])
                     obj.push
                       type: "error",
                       text: match[4],
-                      filePath: path.join(atom.project.getPaths()[0], match[1]),
+                      filePath: file,
                       range: [
-                        [parseInt(match[2]) - 1, parseInt(match[3]) - 1],
-                        [parseInt(match[2]) - 1, parseInt(match[3]) + 300]
+                        [row, column],
+                        [row, column + that.getWordLength(textEditor, row, column)]
                       ]
             resolve obj

@@ -75,3 +83,9 @@
             dismissable: true
           handle()
           resolve []
+
+    getWordLength: (textEditor, row, column) ->
+      text = textEditor.getTextInBufferRange([[row, column], [row, column + 100]])
+      match = wordLengthFormat.exec(text)
+      console.log "Text: #{text}, Match: #{JSON.stringify()}"
+      match?[1].length or 100


Add executable property

Add executable property

--- src/firmware/firmrevision.coffee
+++ src/firmware/firmrevision.coffee
@@ -50,6 +50,15 @@
   ###
   @property("builderClasses",
     get: -> Object.freeze(c for c in @_builderClasses)
+  )
+
+  ###*
+  @property {RegExp[]} executables
+    Array of executable items
+  @readonly
+  ###
+  @property("executables",
+    get: -> Object.freeze(e for e in @_executables)
   )

   ###*
@@ -156,6 +165,7 @@
     @_lastModified    = parseInt(obj.lastModified)
     @_builderClasses  =
       (Builder.subclasses.find((c) => c.name == name) for name in obj.builderClasses or [])
+    @_executables     = (new RegExp(e[0], e[1]) for e in obj.executables or [])
     @_beta            = !!obj.beta
     @_obsolete        = !!obj.obsolete
     @_assets          = obj.assets or {}
@@ -173,6 +183,7 @@
       rubicVersion    : @_rubicVersion
       lastModified    : @_lastModified
       builderClasses  : (cls.name for cls in @_builderClasses)
+      executables     : ([e.source, e.flags] for e in @_executables)
       beta            : @_beta
       obsolete        : @_obsolete
       assets          : @_assets


Fix typos

Fix typos

--- grammars/powershell.cson
+++ grammars/powershell.cson
@@ -35,7 +35,7 @@
       '1':
         'name': 'punctuation.definition.herestring.powershell'
     'end': '^"@'
-    'name': 'constant.string.mutliline.doublequoted'
+    'name': 'constant.string.multiline.doublequoted'
   }
   {
     'begin': '\\s*@\''
@@ -43,7 +43,7 @@
       '1':
         'name': 'punctuation.definition.herestring.powershell'
     'end': '^\'@'
-    'name': 'constant.string.mutliline.singlequoted'
+    'name': 'constant.string.multiline.singlequoted'
   }
   {
     'match': '\\b(?i:(0x\\h*)L)'


Prevent perpetual scroll.

Prevent perpetual scroll.

If we miss the keyup event while a smooth scroll is active (because the
focus changes), then we scroll forever.  This stops scrolling on blur.

Fixes #1788.

--- content_scripts/scroller.coffee
+++ content_scripts/scroller.coffee
@@ -136,6 +136,9 @@
         handlerStack.alwaysContinueBubbling =>
           @keyIsDown = false
           @time += 1
+      blur: =>
+        handlerStack.alwaysContinueBubbling =>
+          @time += 1 if event.target == window

   # Return true if CoreScroller would not initiate a new scroll right now.
   wouldNotInitiateScroll: -> @lastEvent?.repeat and Settings.get "smoothScroll"


Add comment re. Math.sign().

Add comment re. Math.sign().

--- content_scripts/scroller.coffee
+++ content_scripts/scroller.coffee
@@ -5,6 +5,10 @@
 activatedElement = null

 # Return 0, -1 or 1: the sign of the argument.
+# NOTE(smblott; 2014/12/17) We would like to use Math.sign().  However, according to this site
+# (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign) Math.sign() was
+# only introduced in Chrome 38.  This caused problems in R1.48 for users with old Chrome installations.  We
+# can replace this with Math.sign() at some point.
 getSign = (val) ->
   if not val
     0


Strip out git tips from commit message

Strip out git tips from commit message

--- lib/git-commit.coffee
+++ lib/git-commit.coffee
@@ -25,10 +25,10 @@
   })

 # FIXME?: maybe I shouldn't use the COMMIT file in .git/
-# TODO?: Strip out the git tips that 'git status' prints in message
 prepFile = (text) ->
   watcher.close() if watcher?
   # format the text to be ignored in the commit message
+  text = text.replace(/\s*\(.*\)\n/g, '')
   text = text.replace(/\n/g, "\n# ")
   # in order to make sure each line doesn't start with a space, the preceding
   #   line should end with a backslash


toBeEqual matcher updated

toBeEqual matcher updated

--- test/customMatchers.coffee
+++ test/customMatchers.coffee
@@ -119,8 +119,16 @@

       toBeEqual: (util, customEqualityTesters)->
         return compare: (actual, expected)->
+          actual   = objectToText(actual)   if actual?
+          expected = objectToText(expected) if expected?
           result = {}
-          result.pass = actual? && expected? && objectToText(actual) == objectToText(expected)
+          result.pass = actual == expected
+          result
+          result.message = 'Expect '
+          result.message += actual
+          result.message += ' not' if result.pass
+          result.message += ' to be equal '
+          result.message += expected
           result

       toBeADslProvider: (util, customEqualityTesters)->


add edge case for hitting back button and activating nav

--- documentation/app/static/scripts/modules/process-documentation.coffee
+++ documentation/app/static/scripts/modules/process-documentation.coffee
@@ -45,21 +45,22 @@
       imgLength = $imgs.length
       imgLoaded = 0

-      if imgLength is 0
+      initOrRefresh = =>
         if type is 'init'
           scrollSpy.init()
         else
           $('body').scrollspy 'refresh'
+          $(@elements.nav).find('a[href="'+hash+'"]').parent().addClass 'active'
+
+      if imgLength is 0
+        initOrRefresh()
         @scrollToHash(hash, 50)
       else
         $imgs.on 'load', =>
           imgLoaded++

           if imgLoaded is imgLength
-            if type is 'init'
-              scrollSpy.init()
-            else
-              $('body').scrollspy 'refresh'
+            initOrRefresh()
             @scrollToHash(hash)

     addTableWrapper: ->


Modified abstracter to fit inherited classes of phoneCommunicationLog

--- server/lib/abstracter.coffee
+++ server/lib/abstracter.coffee
@@ -28,13 +28,12 @@

             when 'phoneCall'
                 pdta = '?log'
-                concrete.push s: '?log', p: '<a>', o: 'prcd:PhoneCommunicationLog'
+                concrete.push s: '?log', p: '<a>', o: 'pcll:PhoneCall'
                 #concrete.push s: '?log', p: 'prcd:ComType', o: 'VOICE'

             when 'phoneText'
                 pdta = '?log'
-                concrete.push s: '?log', p: '<a>', o: 'prcd:PhoneCommunicationLog'
-                #concrete.push s: '?log', p: 'prcd:ComType', o: 'SMS'
+                concrete.push s: '?log', p: '<a>', o: 'txt:TextMessage'

             when 'myself'
                 abstract.push s: '?x', p: 'pdta:isOutbound', o:'true'


reset to previous type and mods when failed

--- resources/assets/coffee/react/beatmapset-page/main.coffee
+++ resources/assets/coffee/react/beatmapset-page/main.coffee
@@ -74,6 +74,13 @@
   }) =>
     @scoreboardXhr?.abort()

+    prevScoreboardType = @state.currentScoreboardType
+
+    @setState
+      currentScoreboardType: scoreboardType
+
+    prevEnabledMods = @state.enabledMods
+
     enabledMods = if resetMods
       []
     else if enabledMod != null && _.includes @state.enabledMods, enabledMod
@@ -83,11 +90,12 @@
     else
       @state.enabledMods

+    @setState
+      enabledMods: enabledMods
+
     if !@state.currentBeatmap.is_scoreable || (!currentUser.is_supporter && (scoreboardType != 'global' || enabledMods.length > 0))
       @setState
         scores: []
-        currentScoreboardType: scoreboardType
-        enabledMods: enabledMods
       return

     @scoresCache ?= {}
@@ -98,8 +106,6 @@
         scores: @scoresCache[cacheKey].scores
         userScore: @scoresCache[cacheKey].userScore if @scoresCache[cacheKey].userScore?
         userScorePosition: @scoresCache[cacheKey].userScorePosition
-        currentScoreboardType: scoreboardType
-        enabledMods: enabledMods

     if !forceReload && @scoresCache[cacheKey]?
       loadScore()
@@ -121,6 +127,10 @@
       loadScore()

     .fail (xhr, status) =>
+      @setState
+        currentScoreboardType: prevScoreboardType
+        enabledMods: prevEnabledMods
+
       if status == 'abort'
         return


Simplified some of scoring.coffee and dramatically reduced the amount of database work recordTwoGames is doing.

--- server/queues/scoring.coffee
+++ server/queues/scoring.coffee
@@ -37,7 +37,7 @@
   taskPair = req.body.sessions
   addPairwiseTaskToQueue req.body.sessions, (err, success) ->
     if err? then return errors.serverError res, "There was an error adding pairwise tasks: #{err}"
-    sendResponseObject req, res, {'message': 'All task pairs were succesfully sent to the queue'}
+    sendResponseObject req, res, {message: 'All task pairs were succesfully sent to the queue'}

 addPairwiseTaskToQueue = (taskPair, cb) ->
   LevelSession.findOne(_id: taskPair[0]).lean().exec (err, firstSession) =>
@@ -75,7 +75,7 @@
     result = _.sample result, 10
     async.each result, resimulateSession.bind(@, originalLevelID, levelMajorVersion), (err) ->
       if err? then return errors.serverError res, err
-      sendResponseObject req, res, {'message': 'All task pairs were succesfully sent to the queue'}
+      sendResponseObject req, res, {message: 'All task pairs were succesfully sent to the queue'}

 resimulateSession = (originalLevelID, levelMajorVersion, session, cb) =>
   sessionUpdateObject =
@@ -114,7 +114,7 @@
     return callback err, null unless startDate
     now = new Date()
     interval = now - startDate
-    cutoff = new Date now - Math.pow(Math.random(), 2) * interval
+    cutoff = new Date now - Math.pow(Math.random(), 4) * interval
     queryParams.submitDate = $gte: startDate, $lt: cutoff
     selection = 'team totalScore transpiledCode submittedCodeLanguage teamSpells levelID creatorName creator submitDate'
     LevelSession.findOne(queryParams).sort(submitDate: -1).select(selection).lean().exec (err, session) ->
@@ -184,14 +184,13 @@

   yetiGuru = clientResponseObject: req.body, isRandomMatch: true
   async.waterfall [
-    fetchLevelSession.bind(yetiGuru)
-    updateSessions.bind(yetiGuru)
-    indexNewScoreArray.bind(yetiGuru)
-    addMatchToSessions.bind(yetiGuru)
+    calculateSessionScores.bind(yetiGuru)  # Fetches a few small properties from both sessions, prepares @levelSessionUpdates with the score part
+    indexNewScoreArray.bind(yetiGuru)  # Creates and returns @newScoresObject, no query
+    addMatchToSessionsAndUpdate.bind(yetiGuru)  # Adds matches to the session updates and does the writes
     updateUserSimulationCounts.bind(yetiGuru, req.user?._id)
   ], (err, successMessageObject) ->
-    if err? then return errors.serverError res, "There was an error recording the single game:#{err}"
-    sendResponseObject req, res, {'message': 'The single game was submitted successfully!'}
+    if err? then return errors.serverError res, "There was an error recording the single game: #{err}"
+    sendResponseObject req, res, {message: 'The single game was submitted successfully!'}

 module.exports.createNewTask = (req, res) ->
   requestSessionID = req.body.session
@@ -301,7 +300,7 @@
     if taskPairError? then return callback taskPairError
     #console.log 'Sent task pairs to the queue!'
     #console.log taskPairs
-    callback null, {'message': 'All task pairs were succesfully sent to the queue'}
+    callback null, {message: 'All task pairs were succesfully sent to the queue'}

 module.exports.dispatchTaskToConsumer = (req, res) ->
   yetiGuru = {}
@@ -393,9 +392,9 @@
       fetchLevelSession.bind(yetiGuru)
       checkSubmissionDate.bind(yetiGuru)
       logTaskComputation.bind(yetiGuru)
-      updateSessions.bind(yetiGuru)
+      calculateSessionScores.bind(yetiGuru)
       indexNewScoreArray.bind(yetiGuru)
-      addMatchToSessions.bind(yetiGuru)
+      addMatchToSessionsAndUpdate.bind(yetiGuru)
       updateUserSimulationCounts.bind(yetiGuru, req.user?._id)
       determineIfSessionShouldContinueAndUpdateLog.bind(yetiGuru)
       findNearestBetterSessionID.bind(yetiGuru)
@@ -404,15 +403,15 @@
       if err is 'shouldn\'t continue'
         markSessionAsDoneRanking originalSessionID, (err) ->
           if err? then return sendResponseObject req, res, {'error': 'There was an error marking the session as done ranking'}
-          sendResponseObject req, res, {'message': 'The scores were updated successfully, person lost so no more games are being inserted!'}
+          sendResponseObject req, res, {message: 'The scores were updated successfully, person lost so no more games are being inserted!'}
       else if err is 'no session was found'
         markSessionAsDoneRanking originalSessionID, (err) ->
           if err? then return sendResponseObject req, res, {'error': 'There was an error marking the session as done ranking'}
-          sendResponseObject req, res, {'message': 'There were no more games to rank (game is at top)!'}
+          sendResponseObject req, res, {message: 'There were no more games to rank (game is at top)!'}
       else if err?
         errors.serverError res, "There was an error:#{err}"
       else
-        sendResponseObject req, res, {'message': 'The scores were updated successfully and more games were sent to the queue!'}
+        sendResponseObject req, res, {message: 'The scores were updated successfully and more games were sent to the queue!'}
   catch e
     errors.serverError res, 'There was an error processing the task result!'

@@ -422,39 +421,26 @@
     callback 'The response to that query is required to be a JSON object.'
   else
     @clientResponseObject = responseObject
-
-    #log.info 'Verified client response!'
     callback null, responseObject

 fetchTaskLog = (responseObject, callback) ->
-  query = TaskLog.findOne _id: responseObject.taskID
-  query.exec (err, taskLog) =>
+  TaskLog.findOne(_id: responseObject.taskID).lean().exec (err, taskLog) =>
     return callback new Error("Couldn't find TaskLog for _id #{responseObject.taskID}!") unless taskLog
     @taskLog = taskLog
-    #log.info 'Fetched task log!'
-    callback err, taskLog.toObject()
+    callback err, taskLog

 checkTaskLog = (taskLog, callback) ->
   if taskLog.calculationTimeMS then return callback 'That computational task has already been performed'
   if hasTaskTimedOut taskLog.sentDate then return callback 'The task has timed out'
-  #log.info 'Checked task log'
   callback null

 deleteQueueMessage = (callback) ->
   scoringTaskQueue.deleteMessage @clientResponseObject.receiptHandle, (err) ->
-    #log.info 'Deleted queue message'
     callback err

 fetchLevelSession = (callback) ->
-  findParameters =
-    _id: @clientResponseObject.originalSessionID
-
-  query = LevelSession
-  .findOne(findParameters)
-  .lean()
-  query.exec (err, session) =>
+  LevelSession.findOne(_id: @clientResponseObject.originalSessionID).select('submitDate creator level standardDeviation meanStrength totalScore submittedCodeLanguage').lean().exec (err, session) =>
     @levelSession = session
-    #log.info 'Fetched level session'
     callback err

 checkSubmissionDate = (callback) ->
@@ -462,58 +448,45 @@
   if Number(supposedSubmissionDate) isnt Number(@levelSession.submitDate)
     callback 'The game has been resubmitted. Removing from queue...'
   else
-    #log.info 'Checked submission date'
     callback null

 logTaskComputation = (callback) ->
   @taskLog.set('calculationTimeMS', @clientResponseObject.calculationTimeMS)
-  @taskLog.set('sessions')
+  @taskLog.set('sessions')  # Huh?
   @taskLog.calculationTimeMS = @clientResponseObject.calculationTimeMS
   @taskLog.sessions = @clientResponseObject.sessions
   @taskLog.save (err, saved) ->
-    #log.info 'Logged task computation'
     callback err

-updateSessions = (callback) ->
+calculateSessionScores = (callback) ->
   sessionIDs = _.pluck @clientResponseObject.sessions, 'sessionID'
-
   async.map sessionIDs, retrieveOldSessionData, (err, oldScores) =>
-    if err? then callback err, {'error': 'There was an error retrieving the old scores'}
+    if err? then callback err, {error: 'There was an error retrieving the old scores'}
     try
       oldScoreArray = _.toArray putRankingFromMetricsIntoScoreObject @clientResponseObject, oldScores
       newScoreArray = bayes.updatePlayerSkills oldScoreArray
-      saveNewScoresToDatabase newScoreArray, callback
+      createSessionScoreUpdate.call @, scoreObject for scoreObject in newScoreArray
+      callback err, newScoreArray
     catch e
       callback e

-saveNewScoresToDatabase = (newScoreArray, callback) ->
-  async.eachSeries newScoreArray, updateScoreInSession, (err) ->
-    #log.info 'Saved new scores to database'
-    callback err, newScoreArray
-
-updateScoreInSession = (scoreObject, callback) ->
-  LevelSession.findOne {'_id': scoreObject.id}, (err, session) ->
-    if err? then return callback err, null
-
-    session = session.toObject()
-    newTotalScore = scoreObject.meanStrength - 1.8 * scoreObject.standardDeviation
-    scoreHistoryAddition = [Date.now(), newTotalScore]
-    updateObject =
-      meanStrength: scoreObject.meanStrength
-      standardDeviation: scoreObject.standardDeviation
-      totalScore: newTotalScore
-      $push: {scoreHistory: {$each: [scoreHistoryAddition], $slice: -1000}}
-      randomSimulationIndex: Math.random()
-
-    LevelSession.update {'_id': scoreObject.id}, updateObject, callback
-    #log.info "New total score for session #{scoreObject.id} is #{updateObject.totalScore}"
+createSessionScoreUpdate = (scoreObject) ->
+  newTotalScore = scoreObject.meanStrength - 1.8 * scoreObject.standardDeviation
+  scoreHistoryAddition = [Date.now(), newTotalScore]
+  @levelSessionUpdates ?= {}
+  @levelSessionUpdates[scoreObject.id] =
+    meanStrength: scoreObject.meanStrength
+    standardDeviation: scoreObject.standardDeviation
+    totalScore: newTotalScore
+    $push: {scoreHistory: {$each: [scoreHistoryAddition], $slice: -1000}}
+    randomSimulationIndex: Math.random()

 indexNewScoreArray = (newScoreArray, callback) ->
   newScoresObject = _.indexBy newScoreArray, 'id'
   @newScoresObject = newScoresObject
   callback null, newScoresObject

-addMatchToSessions = (newScoreObject, callback) ->
+addMatchToSessionsAndUpdate = (newScoreObject, callback) ->
   matchObject = {}
   matchObject.date = new Date()
   matchObject.opponents = {}
@@ -528,7 +501,7 @@
     match.metrics.rank = Number(newScoreObject[sessionID]?.gameRanking ? 0)
     match.codeLanguage = newScoreObject[sessionID].submittedCodeLanguage

-  #log.info "Match object computed, result: #{matchObject}"
+  #log.info "Match object computed, result: #{JSON.stringify(matchObject, null, 2)}"
   #log.info 'Writing match object to database...'
   #use bind with async to do the writes
   sessionIDs = _.pluck @clientResponseObject.sessions, 'sessionID'
@@ -546,13 +519,10 @@
   currentMatchObject.codeLanguage = matchObject.opponents[opponentsArray[0].sessionID].codeLanguage
   #currentMatchObject.simulator = @clientResponseObject.simulator  # Uncomment when actively debugging simulation mismatches
   #currentMatchObject.randomSeed = parseInt(@clientResponseObject.randomSeed or 0, 10)  # Uncomment when actively debugging simulation mismatches
-  LevelSession.findOne {'_id': sessionID}, (err, session) ->
-    session = session.toObject()
-    #currentMatchObject.playtime = session.playtime ? 0  # Not useful if we can only see last 200
-    sessionUpdateObject =
-      $push: {matches: {$each: [currentMatchObject], $slice: -200}}
-    #log.info "Updating session #{sessionID}"
-    LevelSession.update {'_id': sessionID}, sessionUpdateObject, callback
+  sessionUpdateObject = @levelSessionUpdates[sessionID]
+  sessionUpdateObject.$push.matches = {$each: [currentMatchObject], $slice: -200}
+  #log.info "Update is #{JSON.stringify(sessionUpdateObject, null, 2)}"
+  LevelSession.update {_id: sessionID}, sessionUpdateObject, callback

 updateUserSimulationCounts = (reqUserID, callback) ->
   incrementUserSimulationCount reqUserID, 'simulatedBy', (err) =>
@@ -575,20 +545,16 @@
   sessionID = @clientResponseObject.originalSessionID
   sessionRank = parseInt @clientResponseObject.originalSessionRank

-  queryParameters =
-    _id: sessionID
-
-  updateParameters =
-    '$inc': {}
+  queryParameters = _id: sessionID
+  updateParameters = '$inc': {}

   if sessionRank is 0
     updateParameters['$inc'] = {numberOfWinsAndTies: 1}
   else
     updateParameters['$inc'] = {numberOfLosses: 1}

-  LevelSession.findOneAndUpdate queryParameters, updateParameters, {select: 'numberOfWinsAndTies numberOfLosses'}, (err, updatedSession) ->
+  LevelSession.findOneAndUpdate queryParameters, updateParameters, {select: 'numberOfWinsAndTies numberOfLosses', lean: true}, (err, updatedSession) ->
     if err? then return cb err, updatedSession
-    updatedSession = updatedSession.toObject()

     totalNumberOfGamesPlayed = updatedSession.numberOfWinsAndTies + updatedSession.numberOfLosses
     if totalNumberOfGamesPlayed < 10
@@ -641,10 +607,10 @@
     selectString = '_id totalScore'

     query = LevelSession.findOne(queryParameters)
-    .sort(sortParameters)
-    .limit(limitNumber)
-    .select(selectString)
-    .lean()
+      .sort(sortParameters)
+      .limit(limitNumber)
+      .select(selectString)
+      .lean()

     #console.log "Finding session with score near #{opponentSessionTotalScore}"
     query.exec (err, session) ->
@@ -654,9 +620,9 @@
       cb err, session._id

 retrieveAllOpponentSessionIDs = (sessionID, cb) ->
-  query = LevelSession.findOne({'_id': sessionID})
-  .select('matches.opponents.sessionID matches.date submitDate')
-  .lean()
+  query = LevelSession.findOne({_id: sessionID})
+    .select('matches.opponents.sessionID matches.date submitDate')
+    .lean()
   query.exec (err, session) ->
     if err? then return cb err, null
     opponentSessionIDs = (match.opponents[0].sessionID for match in session.matches when match.date > session.submitDate)
@@ -711,21 +677,24 @@
   return scoreObject

 retrieveOldSessionData = (sessionID, callback) ->
-  LevelSession.findOne {'_id': sessionID}, (err, session) ->
+  formatOldScoreObject = (session) ->
+    standardDeviation: session.standardDeviation ? 25/3
+    meanStrength: session.meanStrength ? 25
+    totalScore: session.totalScore ? (25 - 1.8*(25/3))
+    id: sessionID
+    submittedCodeLanguage: session.submittedCodeLanguage
+
+  return formatOldScoreObject @levelSession if sessionID is @levelSession?._id  # No need to fetch again
+
+  query = _id: sessionID
+  selection = 'standardDeviation meanStrength totalScore submittedCodeLanguage'
+  LevelSession.findOne(query).select(selection).lean().exec (err, session) ->
     return callback err, {'error': 'There was an error retrieving the session.'} if err?
-
-    session = session.toObject()
-    oldScoreObject =
-      'standardDeviation': session.standardDeviation ? 25/3
-      'meanStrength': session.meanStrength ? 25
-      'totalScore': session.totalScore ? (25 - 1.8*(25/3))
-      'id': sessionID
-      'submittedCodeLanguage': session.submittedCodeLanguage
-    callback err, oldScoreObject
+    callback err, formatOldScoreObject session

 markSessionAsDoneRanking = (sessionID, cb) ->
   #console.log 'Marking session as done ranking...'
-  LevelSession.update {'_id': sessionID}, {'isRanking': false}, cb
+  LevelSession.update {_id: sessionID}, {isRanking: false}, cb

 simulatorIsTooOld = (req, res) ->
   clientSimulator = req.body.simulator


Patching bug if newScoreObject is incomplete

--- server/queues/scoring.coffee
+++ server/queues/scoring.coffee
@@ -449,7 +449,7 @@
     matchObject.opponents[sessionID].name = session.name
     matchObject.opponents[sessionID].totalScore = session.totalScore
     matchObject.opponents[sessionID].metrics = {}
-    matchObject.opponents[sessionID].metrics.rank = Number(newScoreObject[sessionID].gameRanking)
+    matchObject.opponents[sessionID].metrics.rank = Number(newScoreObject[sessionID]?.gameRanking ? 0)

   #log.info "Match object computed, result: #{matchObject}"
   #log.info "Writing match object to database..."


Increased scoring task timeout to 10 minutes

Temporary increase until we can increase it only for Brawlwood

--- server/queues/scoring.coffee
+++ server/queues/scoring.coffee
@@ -13,7 +13,7 @@
 bayes = new (require 'bayesian-battle')()

 scoringTaskQueue = undefined
-scoringTaskTimeoutInSeconds = 240
+scoringTaskTimeoutInSeconds = 600

 module.exports.setup = (app) -> connectToScoringQueue()


Search for vocab

Search for vocab

--- src/components/search.cjsx
+++ src/components/search.cjsx
@@ -5,33 +5,49 @@

 Search = React.createClass
   propTypes:
-    history: React.PropTypes.object.isRequired
+    location: React.PropTypes.object.isRequired

   displayExercise: (id) ->
-    @props.history.push("/exercises/#{id}")
+    @props.location.visitExercise(id)
+
+  displayVocab: (id) ->
+    @props.location.visitVocab(id)

   loadExercise: (exerciseId) ->
     @setState({exerciseId})
     ExerciseStore.once 'loaded', @displayExercise
     ExerciseActions.load(exerciseId)

+  loadVocabulary: (vocabId) ->
+    @setState({vocabId})
+    ExerciseStore.once 'loaded', @displayVocab
+    ExerciseActions.load(vocabId)
+
   onFindExercise: ->
     @loadExercise(this.refs.exerciseId.getDOMNode().value)

-  onFindKeyPress: (ev) ->
+  onFindVocabulary: ->
+    @loadVocabulary(this.refs.vocabularyId.getDOMNode().value)
+
+  onExerciseKeyPress: (ev) ->
     return unless ev.key is 'Enter'
     @loadExercise(this.refs.exerciseId.getDOMNode().value)
     ev.preventDefault()

+  onVocabKeyPress: (ev) ->
+    return unless ev.key is 'Enter'
+    @loadVocabulary(this.refs.vocabularyId.getDOMNode().value)
+    ev.preventDefault()
+
   render: ->

-
We be seeeaarching...

+
Edit exercise:

       <BS.Row>
         <BS.Col sm=3>

             <input type="text" autoFocus
               className="form-control"
-              onKeyPress={@onFindKeyPress}
+              onKeyPress={@onExerciseKeyPress}
               ref="exerciseId" placeholder="Exercise ID"/>

               <button className="btn btn-default load"
@@ -41,6 +57,22 @@

         </BS.Col>
       </BS.Row>
+
Edit vocabulary term:

+      <BS.Row>
+        <BS.Col sm=3>
+

+            <input type="text" autoFocus
+              className="form-control"
+              onKeyPress={@onVocabKeyPress}
+              ref="vocabularyId" placeholder="Vocabulary ID"/>
+
+              <button className="btn btn-default load"
+                type="button" onClick={@onFindVocabulary}
+              >Load</button>
+
+

+        </BS.Col>
+      </BS.Row>


autorefresh seats

autorefresh seats

--- app/assets/js/main.coffee
+++ app/assets/js/main.coffee
@@ -212,7 +212,7 @@
     scope:
         showId: '@'
         orderId: '@'
-    controller: ($scope, $http) ->
+    controller: ($scope, $http, $interval) ->
       updatePlan = (plan) ->
         $scope.plan = plan
         $scope.rows = plan.rows
@@ -230,8 +230,14 @@
       $scope.cancel = () ->
         console.log('cancel')

-      $http.get(jsRoutes.controllers.Orders.ajaxFloorplan($scope.showId, $scope.orderId).url).success (updatePlan)
-
+      fetchAndUpdate = ->
+        $http.get(jsRoutes.controllers.Orders.ajaxFloorplan($scope.showId, $scope.orderId).url).success (updatePlan)
+
+
+      refreshTimer = $interval(fetchAndUpdate, 5000);
+      $scope.$on('$destroy', -> $interval.cancel(refreshTimer) );
+
+      fetchAndUpdate()

 CounterInput = angular.module("counterInput", [])


deserialize correctly

deserialize correctly

--- lib/index.coffee
+++ lib/index.coffee
@@ -163,9 +163,13 @@
     args.filename = prefixPath stringify(args.filename)

     @getFile args.filename, args.headers, (err, res) ->
-      chunks = []
-      res.on 'data', (chunk) -> chunks.push chunk
-      res.on 'end', -> args.cb null, JSON.parse(chunks)
+      return args.cb err if err
+      chunks = ''
+
+      res.on 'data', (chunk) ->
+        chunks += chunk.toString 'utf8'
+      res.on 'end', ->
+        args.cb null, JSON.parse chunks
       res.on 'error', args.cb

   getGzip: ->


test the insanity

test the insanity

--- test/stringify.test.coffee
+++ test/stringify.test.coffee
@@ -29,6 +29,7 @@
   # it's actually supposed to be a float.
   # TODO: This doesn't work for floats nested in places,
   # and there's got to be a better way...
+  assert.eql '1:12#', TNETS.stringify(12.0)
   assert.eql '2:12^', TNETS.stringify(12.0, type: 'float')

 test 'stringifying an array', ->


fixes #11

fixes #11

--- menus/ensime.cson
+++ menus/ensime.cson
@@ -1,16 +0,0 @@
-# See https://atom.io/docs/latest/creating-a-package#menus for more details
-'context-menu':
-  '.overlayer':
-      'Enable ensime': 'ensime:toggle'
-
-'menu': [
-  {
-    'label': 'Packages'
-    'submenu': [
-      'label': 'ensime'
-      'submenu': [
-        { 'label': 'Toggle', 'command': 'ensime:toggle' }
-      ]
-    ]
-  }
-]


a minor fix

a minor fix

--- app/locale/zh-HANS.coffee
+++ app/locale/zh-HANS.coffee
@@ -331,7 +331,7 @@
     tip_source_code: "我想改变世界，但是他们不给我源代码。"
     tip_javascript_java: "Java和JavaScript的关系就好比是马和马云。 - Chris Heilmann"
     tip_move_forward: "不管您做什么，前进就对了。 - Martin Luther King Jr."
-    tip_google: "有问题不能解决？百(goo)度(gle)一下！" ＃not sure China can use google?
+    tip_google: "有问题不能解决？百(goo)度(gle)一下！"
     tip_adding_evil: "增加一个邪恶掐捏"
     tip_hate_computers: "那些认为他们讨厌电脑的人，其实他们讨厌的是垃圾程序编写员。- Larry Niven"
     tip_open_source_contribute: "您可以帮助「CodeCombat」进步！"


log user_id when clearing project

log user_id when clearing project

--- services/clsi/app/coffee/ProjectPersistenceManager.coffee
+++ services/clsi/app/coffee/ProjectPersistenceManager.coffee
@@ -37,7 +37,7 @@
 					callback() # ignore any errors from deleting directories

 	clearProject: (project_id, user_id, callback = (error) ->) ->
-		logger.log project_id: project_id, "clearing project for user"
+		logger.log project_id: project_id, user_id:user_id, "clearing project for user"
 		CompileManager.clearProject project_id, user_id, (error) ->
 			return callback(error) if error?
 			ProjectPersistenceManager.clearProjectFromCache project_id, (error) ->


added logger.info to test setup

added logger.info to test setup


Mock out logger in tests

Mock out logger in tests

--- services/clsi/test/unit/coffee/ResourceWriterTests.coffee
+++ services/clsi/test/unit/coffee/ResourceWriterTests.coffee
@@ -12,6 +12,7 @@
 			"./UrlCache" : @UrlCache = {}
 			"mkdirp" : @mkdirp = sinon.stub().callsArg(1)
 			"./OutputFileFinder": @OutputFileFinder = {}
+			"logger-sharelatex": {log: sinon.stub(), err: sinon.stub()}
 			"./Metrics": @Metrics =
 				Timer: class Timer
 					done: sinon.stub()


Provide about and other static pages during installation wizard.

--- client/base.coffee
+++ client/base.coffee
@@ -113,7 +113,7 @@

 if INSTALL
   Meteor.Router.add
-    '*': ->
+    '/': ->
       setSession()
       'install'

@@ -183,30 +183,6 @@
         redirectAnnotationId annotationId
         'redirecting'

-    '/about':
-      as: 'about'
-      to: ->
-        setSession()
-        'about'
-
-    '/help':
-      as: 'help'
-      to: ->
-        setSession()
-        'help'
-
-    '/privacy':
-      as: 'privacy'
-      to: ->
-        setSession()
-        'privacy'
-
-    '/terms':
-      as: 'terms'
-      to: ->
-        setSession()
-        'terms'
-
     '/admin':
       as: 'admin'
       to: ->
@@ -214,9 +190,34 @@
           adminActive: true
         'admin'

-    '*': ->
-      setSession()
-      'notfound'
+Meteor.Router.add
+  '/about':
+    as: 'about'
+    to: ->
+      setSession()
+      'about'
+
+  '/help':
+    as: 'help'
+    to: ->
+      setSession()
+      'help'
+
+  '/privacy':
+    as: 'privacy'
+    to: ->
+      setSession()
+      'privacy'
+
+  '/terms':
+    as: 'terms'
+    to: ->
+      setSession()
+      'terms'
+
+  '*': ->
+    setSession()
+    'notfound'

 # TODO: Use real parser (arguments can be listed multiple times, arguments can be delimited by ";")
 parseQuery = (qs) ->


Add comment

Add comment

--- lib/navigation.coffee
+++ lib/navigation.coffee
@@ -24,7 +24,7 @@
   @controllerFilePath: (model) ->
     "app/controllers/#{AR.pluralize(model)}_controller.rb"

-  # Given a model name, returns the file path for the respective helper
+  # Given a model name, returns the file path for the respective helper
   @helperFilePath: (model) ->
     "app/helpers/#{AR.pluralize(model)}_helper.rb"


Retry delayed definitions multiple times.

--- lib.coffee
+++ lib.coffee
@@ -2,6 +2,7 @@

 RESERVED_FIELDS = ['document', 'parent']
 INVALID_TARGET = "Invalid target document"
+MAX_RETRIES = 1000

 isPlainObject = (obj) ->
   if not _.isObject(obj) or _.isArray(obj) or _.isFunction(obj)
@@ -61,6 +62,11 @@

   collection

+loopMax = (f) ->
+  for i in [0..MAX_RETRIES]
+    throw "Possible infinite loop" if i is MAX_RETRIES
+    break unless f()
+
 class globals.Document
   # TODO: When we will require all fields to be specified and have validator support to validate new objects, we can also run validation here and check all data, reference fields and others
   @objectify: (parent, ancestorArray, obj, fields) ->
@@ -319,6 +325,8 @@
     for document in delayed
       delete document.Meta._delayIndex

+    processedCount = 0
+
     for document in delayed
       assert not document.Meta._listIndex?

@@ -372,7 +380,11 @@

       assert not document.Meta._replaced

+      processedCount++
+
     @_setDelayedCheck()
+
+    processedCount

   @_addDelayed: (document) ->
     @_clearDelayedCheck()
@@ -440,7 +452,8 @@
     @documents = new @_Manager @Meta

     @_addDelayed @
-    @_retryDelayed()
+    loopMax =>
+      @_retryDelayed()

   @list = []
   @_delayed = []
@@ -452,7 +465,8 @@
       @_validateFields document.Meta.fields

   @defineAll: (dontThrowDelayedErrors) ->
-    globals.Document._retryDelayed not dontThrowDelayedErrors
+    loopMax =>
+      globals.Document._retryDelayed not dontThrowDelayedErrors
     globals.Document.validateAll()

     assert dontThrowDelayedErrors or globals.Document._delayed.length is 0


updating add command configuration.

updating add command configuration.

--- lib/marie.coffee
+++ lib/marie.coffee
@@ -6,9 +6,11 @@
 ui = require './ui'

 class Marie
+	@args
 	@app
 	@dir
 	@root
+	@commands
 	@startTime
 	@endTime
 	@initTime
@@ -33,21 +35,43 @@
 		LOCAL: 'localMongodbServer'
 		REMOTE: 'someMongodbServer'
 		URL: 'someMongodbServerWithURL'
-
+

 	constructor: ->
 		@startTime = new Date
 		@root = process.cwd()
 		prompt.message = null
-		@parseArgs()
-
-
-	parseArgs: ->
-		args = process.argv
-		len = args.length
+		@configureCommands()
+		@configureArgs()
+
+
+	configureCommands: ->
+		@commands =
+			'new': @add
+			'update': @update
+			'list': @list
+			'remove': @remove
+			'start': @start
+			'stop': @start
+
+
+	configureArgs: ->
+		@args = process.argv
+		len = @args.length
 		if len >= 3
-			@app = args[args.length-1]
-			@configure()
+			cmd = @args[2]
+			if @commands[cmd]? then @commands[cmd](@args[3]) else @add cmd
+		else
+			@add null
+
+
+	add: (app) =>
+		if not not app
+			@app = app
+			ui.header 'Generating', @app
+			@dir = @rootPath "/#{@app}"
+			fs.stat @dir, (err, stats) =>
+				if err then @configureSails() else ui.warn 'App already exists.'
 		else
 			console.log ''
 			ui.warn 'Enter app name.'
@@ -57,15 +81,23 @@
 				if error
 					ui.error 'An error occured.'
 				else
-					@app = result.name
-					@configure()
-
-
-	configure: ->
-		ui.header 'Generating', @app
-		@dir = @rootPath "/#{@app}"
-		fs.stat @dir, (err, stats) =>
-			if err then @configureSails() else ui.warn 'App already exists.'
+					@add result.name
+
+
+	update: (app) =>
+		ui.warn 'update command'
+
+	list: (app) =>
+		ui.warn 'list command'
+
+	remove: (app) =>
+		ui.warn 'remove command'
+
+	start: (app) =>
+		ui.warn 'start command'
+
+	stop: (app) =>
+		ui.warn 'stop command'

 	configureSails: ->


need to redirect with query if there is one for showIntro

--- tutor/src/helpers/conditional-handlers.cjsx
+++ tutor/src/helpers/conditional-handlers.cjsx
@@ -38,7 +38,10 @@
       if CourseStore.isConceptCoach(courseId)
         <CCDashboard  courseId={courseId} {...props} />
       else
-        <Redirect to={TutorRouter.makePathname('viewTeacherDashboard', props.params)}/>
+        <Redirect to={{
+          pathname: TutorRouter.makePathname('viewTeacherDashboard', props.params)
+          query: TutorRouter.currentQuery()
+        }}/>
     else
       if CourseStore.isConceptCoach(courseId)
         <CCStudentRedirect courseId={courseId} />


Fix scaling to maximize size of each group

--- client/tawk.coffee
+++ client/tawk.coffee
@@ -82,16 +82,16 @@
     # when there are only a few people in the space
     if active_connections <= 1
       person_height = Math.max(screen_height - 60, person_height)
-      person_width = Math.max(screen_width / 2 - 60, person_width)
+      person_width = Math.max(screen_width - 60, person_width)
     else if active_connections <= 2
       person_height = Math.max(screen_height - 60, person_height)
-      person_width = Math.max(screen_width / 3 - 60, person_width)
+      person_width = Math.max(screen_width / 2 - 60, person_width)
     else if active_connections <= 4
       person_height = Math.max(screen_height / 2 - 60, person_height)
-      person_width = Math.max(screen_width / 3 - 60, person_width)
+      person_width = Math.max(screen_width / 2 - 60, person_width)
     else if active_connections <= 7
       person_height = Math.max(screen_height / 2 - 60, person_height)
-      person_width = Math.max(screen_width / 4 - 60, person_width)
+      person_width = Math.max(screen_width / 3 - 60, person_width)

     if person_height > person_width * 3 / 4
       person_height = person_width * 3 / 4
@@ -99,8 +99,8 @@
       person_width = person_height * 4 / 3

     _:
-      person_height: Math.round(person_height * 1.5)
-      person_width: Math.round(person_width * 1.5)
+      person_height: Math.round(person_height)
+      person_width: Math.round(person_width)

   bus('tawk/window').to_fetch = (key) ->
     _:


Fix autofill path with affair_id and person_id in products

--- app/assets/javascripts/app/controllers/people/affairs/products.js.coffee
+++ app/assets/javascripts/app/controllers/people/affairs/products.js.coffee
@@ -88,8 +88,8 @@
     @template = { quantity: 1, unit_symbol: "?" }

     if params
-      @template.person_id = params.person_id if params.person_id
-      @template.affair_id = params.affair_id if params.affair_id
+      @person_id = @template.person_id = params.person_id if params.person_id
+      @affair_id = @template.affair_id = params.affair_id if params.affair_id

       @can = params.can if params.can


#1182 Remove irrelevant event

#1182 Remove irrelevant event

--- app/assets/javascripts/oxalis/view/action-bar/volume_actions_view.coffee
+++ app/assets/javascripts/oxalis/view/action-bar/volume_actions_view.coffee
@@ -47,7 +47,6 @@

     mode = @modeMapping[evt.target.id]
     @model.volumeTracing.setMode(mode)
-    app.vent.trigger("changeVolumeMode", mode)

   serializeData : ->


Deal with errors

Deal with errors

--- apps/home_2/routes.coffee
+++ apps/home_2/routes.coffee
@@ -36,33 +36,37 @@
   # homepage:featured-shows
   featuredShows = new Items [], id: '530ebe92139b21efd6000071', item_type: 'PartnerShow'

-  Q.allSettled(_.compact([
-    heroUnits.fetch(cache: true, cacheTime: timeToCacheInSeconds)
-    metaphysics query: query, req: req
-    featuredLinks.fetch(cache: true)
-    featuredArticles.fetch
-      cache: true
-      cacheTime: timeToCacheInSeconds
-      data:
-        published: true
-        featured: true
-        sort: '-published_at'
-    featuredShows.fetch(cache: true, cacheTime: timeToCacheInSeconds)
-  ])).spread( (heroUnitPromise, { value }) ->
-    heroUnits[positionWelcomeHeroMethod(req, res)](welcomeHero) unless req.user?
+  Q
+    .all [
+      heroUnits.fetch cache: true, cacheTime: timeToCacheInSeconds
+      metaphysics query: query, req: req
+      featuredLinks.fetch cache: true
+      featuredArticles.fetch
+        cache: true
+        cacheTime: timeToCacheInSeconds
+        data:
+          published: true
+          featured: true
+          sort: '-published_at'
+      featuredShows.fetch cache: true, cacheTime: timeToCacheInSeconds
+    ]

-    res.locals.sd.HERO_UNITS = heroUnits.toJSON()
-    res.locals.sd.USER_HOME_PAGE = value?.home_page_modules
+    .then ([x, { home_page_modules }]) ->
+      heroUnits[positionWelcomeHeroMethod(req, res)](welcomeHero) unless req.user?

-    res.render 'index',
-      heroUnits: heroUnits
-      modules: value?.home_page_modules
-      featuredLinks: featuredLinks
-      featuredArticles: featuredArticles
-      featuredShows: featuredShows
-      viewHelpers: viewHelpers
-      browseCategories: browseCategories
-  ).done()
+      res.locals.sd.HERO_UNITS = heroUnits.toJSON()
+      res.locals.sd.USER_HOME_PAGE = home_page_modules
+
+      res.render 'index',
+        heroUnits: heroUnits
+        modules: home_page_modules
+        featuredLinks: featuredLinks
+        featuredArticles: featuredArticles
+        featuredShows: featuredShows
+        viewHelpers: viewHelpers
+        browseCategories: browseCategories
+
+    .catch next

 @redirectToSignup = (req, res) ->
   res.redirect "/sign_up"