added support for specifying modal width

--- app/assets/javascripts/pt/tags/modal.js.coffee
+++ app/assets/javascripts/pt/tags/modal.js.coffee
@@ -8,6 +8,7 @@
       attr    = $el.ptAttr()
       buttons = []
       title   = attr.modalTitle
+      width    = attr.modalWidth

       if Object.has attr, 'modalSave'
         buttons.push
@@ -41,6 +42,9 @@
           success: (html) ->
             $bootbox = bootbox.dialog html, buttons, { header: title, keyboard: true  }
             $bootbox.find('.modal-body').attr id: 'pt-modal-body'
+            if width
+              $bootbox.css "width", width
+              $bootbox.css "margin-left", ((width/2)*-1)

             # Make close button gray
             if Object.has attr, 'modalSave'


Increase S3 cleanup timeout

Increase S3 cleanup timeout

--- test/suite.coffee
+++ test/suite.coffee
@@ -46,6 +46,8 @@
 # Clean up S3 objects
 if process.env.INTEGRATION_TEST is 'true'
   afterEach (done) ->
+    @timeout 40000
+
     return process.nextTick done if cleanup.length is 0

     upload.s3.deleteObjects Delete: Objects: cleanup, (err) ->


Cleaned up bmi-ahi coffeescript for #166

--- app/assets/javascripts/tools/bmi_ahi.js.coffee
+++ app/assets/javascripts/tools/bmi_ahi.js.coffee
@@ -70,46 +70,46 @@
   x = d3.scale.linear().range([0, w])

   svg = d3.select("#bmi-graph svg.chart")
-  .attr("class", "chart")
-  .attr("height", h + m.top + m.bottom)
-  .attr("width", w + m.right + m.left)
-  .append("g")
-  .attr("transform", "translate("+m.left+","+m.top+")")
+    .attr("class", "chart")
+    .attr("height", h + m.top + m.bottom)
+    .attr("width", w + m.right + m.left)
+    .append("g")
+    .attr("transform", "translate("+m.left+","+m.top+")")

   x.domain([0,45])

   xAxis = d3.svg.axis().scale(x).orient("bottom").tickValues([0, 18.5, 25, 30, 45, bmi])

   svg.append("g")
-  .attr("class", "x axis")
-  .attr("transform", "translate(0,"+h+")")
-  .call(xAxis)
+    .attr("class", "x axis")
+    .attr("transform", "translate(0,"+h+")")
+    .call(xAxis)

   graph = svg.selectAll(".bar").data(data).enter()

   graph.append("rect")
-  .attr("class", (d) -> d.color)
-  .attr("x", (d) -> x(d.from))
-  .attr("width", (d) -> x(d.to - d.from))
-  .attr("height", h)
-  .attr("y", 0)
+    .attr("class", (d) -> d.color)
+    .attr("x", (d) -> x(d.from))
+    .attr("width", (d) -> x(d.to - d.from))
+    .attr("height", h)
+    .attr("y", 0)

   graph.append("text")
-  .style("text-anchor", "middle")
-  .style("font-size", '10px')
-  .style("font-weight", 'bold')
-  .text((d) -> d.label)
-  .attr("x", (d) -> x(((d.to - d.from)/2)+ d.from) )
-  .attr("y", -7)
+    .style("text-anchor", "middle")
+    .style("font-size", '10px')
+    .style("font-weight", 'bold')
+    .text((d) -> d.label)
+    .attr("x", (d) -> x(((d.to - d.from)/2)+ d.from) )
+    .attr("y", -7)

   svg.append('svg:line')
-  .attr('class', 'my_bmi')
-  .attr("x1", x(bmi))
-  .attr("x2", x(bmi))
-  .attr("y1", -1)
-  .attr("y2", h+1)
-  .attr("stroke-width", 2)
-  .attr("stroke", "black")
+    .attr('class', 'my_bmi')
+    .attr("x1", x(bmi))
+    .attr("x2", x(bmi))
+    .attr("y1", -1)
+    .attr("y2", h+1)
+    .attr("stroke-width", 2)
+    .attr("stroke", "black")

@@ -134,10 +134,10 @@

   # Create Graph Area
   svg = d3.select("#ahi-graph svg.chart")
-  .attr("width", w + m.left + m.right)
-  .attr("height", h + m.top + m.bottom)
-  .append("g")
-  .attr("transform", "translate(" + m.left + "," + m.top + ")")
+    .attr("width", w + m.left + m.right)
+    .attr("height", h + m.top + m.bottom)
+    .append("g")
+    .attr("transform", "translate(" + m.left + "," + m.top + ")")

   xa.domain([-20, -10, -5, 5, 10, 20])
   ya.domain([100, -100])
@@ -148,37 +148,37 @@

   # Create X Axis
   svg.append("g")
-  .attr("class", "x axis")
-  .attr("transform", "translate(0," + h + ")")
-  .call(xAxis)
-  .append('text')
-  .attr("x", w/2)
-  .attr("y", 27)
-  .attr("dx", ".71em")
-  .style("text-anchor", "middle")
-  .text('Change in Weight (%)')
+    .attr("class", "x axis")
+    .attr("transform", "translate(0," + h + ")")
+    .call(xAxis)
+    .append('text')
+    .attr("x", w/2)
+    .attr("y", 27)
+    .attr("dx", ".71em")
+    .style("text-anchor", "middle")
+    .text('Change in Weight (%)')

   # Create Y Axis
   svg.append("g")
-  .attr("class", "y axis")
-  .call(yAxis)
-  .append("text")
-  .attr("transform", "rotate(-90)")
-  .attr("y", 0 - m.left)
-  .attr("x", 0 - (h/2))
-  .attr("dy", "1em")
-  .style("text-anchor", "middle")
-  .text("Change in AHI (%)")
+    .attr("class", "y axis")
+    .call(yAxis)
+    .append("text")
+    .attr("transform", "rotate(-90)")
+    .attr("y", 0 - m.left)
+    .attr("x", 0 - (h/2))
+    .attr("dy", "1em")
+    .style("text-anchor", "middle")
+    .text("Change in AHI (%)")

   graph = svg.selectAll(".bar").data(data)

   graph.enter().append("rect")
-  .attr("class", "bar")
-  .attr("x", (d) -> xa(d.weight_change))
-  .attr("width", w/6 - 10)
-  .attr("y", (d) -> d3.min([h - ya(d.ahi_change), ya(0)]))
-  .attr("height", (d) -> Math.abs(ya(d.ahi_change) - ya(0)))
+    .attr("class", "bar")
+    .attr("x", (d) -> xa(d.weight_change))
+    .attr("width", w/6 - 10)
+    .attr("y", (d) -> d3.min([h - ya(d.ahi_change), ya(0)]))
+    .attr("height", (d) -> Math.abs(ya(d.ahi_change) - ya(0)))

   window.xa = xa
   window.ya = ya


Added another few tests, and changed the wording of some others.

--- test/suite.coffee
+++ test/suite.coffee
@@ -105,11 +105,45 @@
     before_each ->
       test_count_a = 0
       test_count_b = 0
-
       suite.add_test new Test -> test_count_a += 1
       suite.add_test new Test -> test_count_b += 1

-    it 'should call each test once when run', ->
+    it 'should run all tests when suite is run', ->
       suite.run()
       test_count_a.should.equal(1)
       test_count_b.should.equal(1)
+
+    # with two tests
+    describe 'and a subsuite', ->
+      subsuite = null
+
+      before_each ->
+        subsuite = new Suite()
+        suite.add_suite subsuite
+
+      it 'should run all tests when suite is run', ->
+        suite.run()
+        test_count_a.should.equal(1)
+        test_count_b.should.equal(1)
+
+      # with two tests
+      # and a subsuite
+      describe 'with a test', ->
+        test_count_c = null
+
+        before_each ->
+          test_count_c = 0
+          subsuite.add_test new Test -> test_count_c += 1
+
+        it 'should run all tests when suite is run', ->
+          suite.run()
+          test_count_a.should.equal(1)
+          test_count_b.should.equal(1)
+          test_count_c.should.equal(1)
+
+        it 'should run only the subsuite test when subsuite is run', ->
+          subsuite.run()
+          test_count_a.should.equal(0)
+          test_count_b.should.equal(0)
+          test_count_c.should.equal(1)
+


Fixed error message in match when subjects syntax is on multiple lines

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -2314,7 +2314,7 @@
       [conds, assigns] = matchBranchTranslate precs, assigns, compiledResult
       [conds, join (findDefinitions ctx, (setToArray (subtractSets lifted, jointlyUsed))),
         assigns])
-    , "throw new Error('match failed to match #{(print subject)}#{errorMessage}');" #TODO: what subject?
+    , "throw new Error(#{toMultilineJsString "match failed to match #{(print subject)}#{errorMessage}"});"

     ctx.setUsedNames join oldUsed, setToArray jointlyUsed

@@ -7622,6 +7622,7 @@
 exports.originOf = originOf
 exports.sortedArgs = sortedArgs

+exports.isNotCapital = isNotCapital
 exports.isForm = isForm
 exports.isCall = isCall
 exports.isAtom = isAtom


Proper typing in resolve and lookup of constraints for deferredly typed calls

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1061,6 +1061,7 @@
     # log "pattern", matched.tea, pattern.tea
     unify ctx, matched.tea.type, pattern.tea.type

+  constraints = matched.tea.constraints
   # log "pattern compiel", definedNames, pattern
   for {name, id, type} in definedNames
     deps = ctx.deferredNames()
@@ -1074,13 +1075,15 @@
       ctx.addToDeferred
         name: name
         type: currentType
-        deps: (map (({name}) -> name), deps)
-      for dep in deps
-        ctx.addToDeferred {name: dep.name, type: dep.type, reversed: name}
+        constraints: constraints
+        polymorphic: polymorphic
+        deps: deps#(map (({name}) -> name), deps)
+      # for dep in deps
+      #   ctx.addToDeferred {name: dep.name, type: dep.type, reversed: name}
       ctx.assignType name, (new TempType type)
     else
       # Ready for typing since there are no missing dependencies
-      inferType ctx, name, type, matched.tea.constraints, polymorphic
+      inferType ctx, name, type, constraints, polymorphic

   precs: precs ? []
   assigns: assigns ? []
@@ -1089,7 +1092,7 @@
   # For explicitly typed bindings, we need to check that the inferred type
   #   corresponds to the annotated
   currentType = substitute ctx.substitution, type
-  if ctx.isTyped name
+  if ctx.isActuallyTyped name
     # TODO: check class constraints
     unify ctx, currentType.type, (freshInstance ctx, ctx.type name).type
   else
@@ -1153,32 +1156,39 @@
   if _notEmpty ctx.deferredBindings()
     groups = toNameSets sortedStronglyConnectedComponents deferredToGraph ctx.deferredBindings()
     for group in groups
-      groupBindings = (binding for binding in ctx.deferredBindings() when inSet group, binding.reversed or binding.name)
-      names = concatConcatMaps map (({name, type}) -> newMapWith name, type), groupBindings
+      bindings = newMap()
+      for binding in ctx.deferredBindings() when inSet group, binding.name
+        added = (lookupOrAdd bindings, binding.name, (types: []))
+        added.types.push binding.type
+        added.constraints = binding.constraints
+        added.polymorphic = binding.polymorphic
+        for dep in binding.deps
+          (lookupOrAdd bindings, dep.name, (types: [])).types.push dep.type
+
+      # names = concatConcatMaps map (({name, type}) -> newMapWith name, type), groupBindings
       # First get rid of instances of already resolved types
       unresolvedNames = newMap()
-      for name, types of values names
+      for name, binding of values bindings
         if canonicalType = ctx.actualType name
-          for type in types
+          for type in binding.types
             unify ctx, type.type, (freshInstance ctx, canonicalType).type
         else
-          addToMap unresolvedNames, name, types
+          addToMap unresolvedNames, name, binding

       # Now assign the same type to all occurences of the given type and unify
-      for name, types of values unresolvedNames
+      for name, binding of values unresolvedNames
         canonicalType = toConstrained ctx.freshTypeVariable star
         definitionConstraints = []
-        for type in types
+        for type in binding.types
           #log type.constructor
-          if _notEmpty type.constraints
-            definitionConstraints = type.constraints
           unify ctx, canonicalType.type, type.type
           # log "done unifying one"
         # TODO: this treatment of constraints won't be enough, we will probably
         # have to redefer them or at least unify somehow
-        unifiedType = (substitute ctx.substitution, canonicalType)
-        ctx.assignType name,
-          quantifyAll (addConstraints unifiedType, definitionConstraints)
+        inferType ctx, name, canonicalType, binding.constraints, binding.polymorphic
+        # unifiedType = (substitute ctx.substitution, canonicalType)
+        # ctx.assignType name,
+        #   quantifyAll (addConstraints unifiedType, definitionConstraints)

 compileDeferred = (ctx) ->
   compiledPairs = []
@@ -2399,7 +2409,7 @@
   {ir: irCallTranslate, type, op, args}

 irCallTranslate = (ctx, {type, op, args}) ->
-  finalType = substitute ctx.substitution, type
+  finalType = addConstraintsFrom ctx, op, (substitute ctx.substitution, type)
   # log op, (printType type), printType finalType
   classParams =
     if op.ir is irReferenceTranslate and ctx.isMethod op.name, op.type
@@ -2413,6 +2423,14 @@
       translateIr ctx, op
   (jsCall op, (join classParams, (translateIr ctx, args)))

+addConstraintsFrom = (ctx, {name, type}, to) ->
+  typed = ctx.type name
+  if typed and _empty to.constraints
+    inferredType = freshInstance ctx, typed
+    sub = matchType inferredType.type, (substitute ctx.substitution, type).type
+    addConstraints to, (substitute sub, inferredType).constraints
+  else
+    to

 irReference = (name, type, arity) ->
   {ir: irReferenceTranslate, name, type, arity}
@@ -2983,12 +3001,11 @@
 deferredToGraph = (deferred) ->
   nodes = newMap()
   findOrAdd = (name) ->
-    (lookupInMap nodes, name) or
-      (addToMap nodes, name, node = {name: name, edges: []}) and node
-  for {name, deps, reversed} in deferred when not reversed
+    lookupOrAdd nodes, name, {name: name, edges: []}
+  for {name, deps} in deferred
     node = findOrAdd name
     for dep in deps
-      node.edges.push findOrAdd dep
+      node.edges.push findOrAdd dep.name
   (mapToArray nodes)

 toNameSets = (vertexGroups) ->
@@ -3500,6 +3517,14 @@
   map.values[key] = value
   map

+lookupOrAdd = (map, key, value) ->
+  if existing = map.values[key]
+    existing
+  else
+    map.size++
+    map.values[key] = value
+    value
+
 removeFromSet =
 removeFromMap = (set, key) ->
   return if !set.values[key]?
@@ -5426,24 +5451,6 @@
     (match b
       True (aliased-show something False)
       False (show something)))
-  """
-  "(== 1 2)", yes
-
-  """recursive overloaded functions safe"""
-  """
-  Show (class [a]
-    show (fn [x] (: (Fn a String))))
-
-  show-string (instance (Show String)
-    show (fn [x] x))
-
-  show-bool (instance (Show Bool)
-    show (fn [x] "Bool"))
-
-  aliased-show (fn [something b]
-    (match b
-      True (aliased-show something False)
-      False (show something)))

   x (aliased-show "Bool" True)
   y (aliased-show True True)


space

space

--- assets/js/src/uploaders/video.coffee
+++ assets/js/src/uploaders/video.coffee
@@ -11,8 +11,8 @@
   # Show placeholder image instead.
   #
   renderMedia: (media_data) ->
-    access_key    = media_data.access_key
-    html = "
<input type='text' placeholder='Caption (optional)' class='caption form-control'>
"
+    access_key = media_data.access_key
+    html       = "
<input type='text' placeholder='Caption (optional)' class='caption form-control'>
"
     @$uploads.append html

 module.exports = Video


Updated upload

Updated upload

In first place indentation is not correct and second I am not really sure that ", false" is needed.

--- main/static/src/script/common/upload.coffee
+++ main/static/src/script/common/upload.coffee
@@ -79,8 +79,8 @@
       @active_files += 1

       xhr.upload.addEventListener 'progress', (event) ->
-          progress(parseInt(event.loaded / event.total * 100.0))
-        , false
+        progress(parseInt(event.loaded / event.total * 100.0))
+

       xhr.onload = (event) =>
         response = JSON.parse(event.currentTarget.response)


Add tests for loader rendering

Add tests for loader rendering

--- test/tests.coffee
+++ test/tests.coffee
@@ -54,3 +54,24 @@
     React.renderComponent (component src: 'test.png', priority: 1), div
     assert.equal queue._loadResults[0].priority(), 1,
       'Expected priority to change to 1'
+
+  it 'renders a loader in a load queue without a src', ->
+    React.renderComponent (component null), div
+    image = div.getElementsByTagName('img')[0]
+    assert.notOk image.attributes.src, 'Expected loader not to have src'
+
+  it 'renders a loader in a load queue with a src when dequeued', (done) ->
+    React.renderComponent React.withContext(
+      loadQueue: queue
+      -> (ReactLoadQueueLoader
+        src: 'test.png'
+        loader: (props) ->
+          if props.src
+            assert.equal props.src, 'test.png', 'Expected loader to have src'
+            done()
+          (img null)
+      )),
+      div
+    image = div.getElementsByTagName('img')[0]
+    assert.notOk image.attributes.src, 'Expected loader not to have src'
+    queue.run()


Querying endpoint for votes by user

Querying endpoint for votes by user

--- resources/votes.coffee
+++ resources/votes.coffee
@@ -16,6 +16,7 @@
     'index'
     create
     getByCompetition
+    getByUser
   ]

 _baseCreate = crud.create.handler
@@ -48,4 +49,15 @@

     @api.list competition_id: competitionId

+getByUser =
+  route: '/by-user/:userId'
+  method: 'GET'
+  filters: [filters.FromUrlParams]
+  handler: ->
+    userId = @params.userId
+    unless userId
+      throw new errors.UserError('must provide userId')
+
+    @api.list user_id: userId
+
 module.exports = VoteResource


update postgres version

update postgres version

--- js/outer/versions.coffee
+++ js/outer/versions.coffee
@@ -77,7 +77,7 @@
   erlang: 'r14b04'

   solr: "4.3.1"
-  postgresql: "9.2.4"
+  postgresql: "9.2.5"
   mysql: "5.5.32"
   mongodb: "2.4.6"
   riak: "1.2.1"


project-manager options

project-manager options

--- config.template.cson
+++ config.template.cson
@@ -18,6 +18,10 @@
     plugins:
       "git-diff": true
       "git-diffDecorationsZIndex": 0
+  "project-manager":
+    alwaysOpenInSameWindow: true
+    environmentSpecificProjects: true
+    savePathsRelativeToHome: true
   "terminal-plus":
     core:
       scrollback: 9999


Part of a brain should be working..

Part of a brain should be working..

--- src/dynamodb-brain.coffee
+++ src/dynamodb-brain.coffee
@@ -2,7 +2,11 @@
 #   A hubot brain built on DynamoDB
 #
 # Configuration:
-#   LIST_OF_ENV_VARS_TO_SET
+# HUBOT_AWS_REGION - Default to us-east-2 if not set
+# HUBOT_BRAIN_DYNAMO_TABLE - DynamoDB table, default hubotbrain
+# HUBOT_DYNAMO_NAME - Brain entry in table, default is our robot name
+# HUBOT_AWS_ACCESS_KEY_ID - AWS Credentials, default to instance IAM
+# HUBOT_AWS_SECRET_ACCESS_KEY - AWS Credentials, default to instance IAM
 #
 # Commands:
 #   hubot brainscan - Check to make sure our brain is working
@@ -14,7 +18,60 @@
 # Author:
 #   Nitehawk <nitehawk@nitespace.co>

+AWS = require 'aws-sdk'
+
+config = {}
+params = {}
+
+creds = {}
+
 module.exports = (robot) ->
+  config.region = process.env.HUBOT_AWS_REGION or 'us-east-2'
+  params.TableName = process.env.HUBOT_BRAIN_DYNAMO_TABLE or 'hubotbrain'
+  params.Key = { botname: process.env.HUBOT_BRAIN_DYNAMO_NAME or robot.name }
+  creds.access = process.env.HUBOT_AWS_ACCESS_KEY_ID or undefined
+  creds.secret = process.env.HUBOT_AWS_SECRET_ACCESS_KEY or undefined
+
+
+  if creds.access and creds.secret
+    config.accessKeyId = creds.access
+    config.secretAccessKey = creds.secret
+
+  AWS.config.update config
+
+  doc = new AWS.DynamoDB.DocumentClient
+
+  saveBrain = (data = {}) ->
+    console.log "Saving brain"
+    brain = {TableName: params.TableName, Item: data}
+    brain.Item.botname = params.botname
+    doc.put brain, (err, res) ->
+      if err
+        robot.logger.error err
+      else
+        robot.logger.error "Brain saved"
+
+        #robot.brain.setAutoSave false
+
+  doc.get params, (err, data) ->
+    if err
+      throw err
+    else if data
+      robot.logger.info "hubot-dynamodb-brain: Data for #{params.key} retrieved from Dynamo"
+      robot.brain.mergeData JSON.parse(data.toString())
+    else
+      robot.logger.info "hubot-dynamodb-brain: Initializing new brain for #{params.key}"
+      robot.brain.mergeData {}
+    robot.brain.setAutoSave true
+
+  robot.brain.on 'save', (data = {}) ->
+    robot.messageRoom "room1", "Saving Brain"
+    console.log "Saving brain"
+    saveBrain data
+
+
   robot.respond /brainscan/, (res) ->
-    res.reply "I have a brain?"
+    console.log "Brainscan - attempt to save brain"
+    saveBrain {lastres: res}
+    res.reply "My brain is doing great!"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_tilemap.coffee
+++ src/engine_tilemap.coffee
@@ -9,6 +9,8 @@
 @param {Object} self Reference to the engine
 ###
 Engine.Tilemap = (I, self) ->
+  Obje
+
   map = null
   updating = false
   clearObjects = false


Only update lines if it is needed

Only update lines if it is needed

--- src/app/editor.coffee
+++ src/app/editor.coffee
@@ -274,6 +274,8 @@
     firstVisibleScreenRow = @getFirstVisibleScreenRow()
     lastVisibleScreenRow = @getLastVisibleScreenRow()

+    return if @firstRenderedScreenRow <= firstVisibleScreenRow and @lastRenderedScreenRow >= lastVisibleScreenRow
+
     @gutter.renderLineNumbers(firstVisibleScreenRow, lastVisibleScreenRow)

     if firstVisibleScreenRow > @firstRenderedScreenRow


Clone serialized view state in editor before modifying it in place

--- src/app/editor.coffee
+++ src/app/editor.coffee
@@ -46,6 +46,7 @@
   editSessions: null

   @deserialize: (viewState, rootView) ->
+    viewState = _.clone(viewState)
     viewState.editSessions = viewState.editSessions.map (editSession) ->
       editSession = _.clone(editSession)
       editSession.buffer = Buffer.deserialize(editSession.buffer, rootView.project)


this feels more honest.

this feels more honest.

If there's a matcher, use the matcher. We shouldn't even
test for equality when we know there's a matcher. In fact,
it's possible someone's matcher will have a side effect that
fails when a true equality test short-circuits it.

--- src/args-match.coffee
+++ src/args-match.coffee
@@ -12,7 +12,10 @@
     argumentMatchesExpectation(expectedArg, actualArgs[i])

 argumentMatchesExpectation = (expectedArg, actualArg) ->
-  _.eq(expectedArg, actualArg) || matcherFor(expectedArg)?(actualArg)
+  if matcher = matcherFor(expectedArg)
+    matcher(actualArg)
+  else
+    _.eq(expectedArg, actualArg)

 matcherFor = (expectedArg) ->
   if _.isFunction(expectedArg?.__matches)


Register object after initialization

Register object after initialization

--- core/KDObject.coffee
+++ core/KDObject.coffee
@@ -12,10 +12,10 @@
     @setDelegate options.delegate if options.delegate
     @registerKDObjectInstance()

+    super
+
     if options.testPath
       KD.registerInstanceForTesting this
-
-    super

     @on 'error', error
     @once 'ready', => @readyState = READY


Update wrap-in-tag.cson

Update wrap-in-tag.cson

--- keymaps/wrap-in-tag.cson
+++ keymaps/wrap-in-tag.cson
@@ -6,6 +6,6 @@
 # the root workspace element.

 # For more detailed documentation see
-# https://atom.io/docs/latest/behind-atom-keymaps-in-depth
+# https://flight-manual.atom.io/behind-atom/sections/keymaps-in-depth/
 'atom-workspace':
   'alt-shift-w': 'wrap-in-tag:wrap'


clean up streaming api logic

clean up streaming api logic

--- src/hubot/campfire.coffee
+++ src/hubot/campfire.coffee
@@ -98,15 +98,32 @@

       request = HTTPS.request options, (response) ->
         response.setEncoding("utf8")
+
+        buf = ''
         response.on "data", (chunk) ->
-          if chunk.match(/^\S+/)
-            console.log "#{new Date}: Received #{id} \"#{chunk}\""
-            try
-              chunk.split("\r").forEach (part) ->
-                data = JSON.parse part
+          if chunk == ' '
+            # campfire api sends a ' ' heartbeat every 3s

-                self.emit data.type, data.id, data.created_at, data.room_id, data.user_id, data.body
-                data
+          else if chunk.match(/^\s*Access Denied/)
+            # errors are not json formatted
+            console.log "Error", chunk
+            process.exit(1)
+
+          else
+            # api uses newline terminated json payloads
+            # buffer across tcp packets and parse out lines
+            buf += chunk
+
+            while (offset = buf.indexOf("\r")) > -1
+              part = buf.substr(0, offset)
+              buf = buf.substr(offset + 1)
+
+              if part
+                try
+                  data = JSON.parse part
+                  self.emit data.type, data.id, data.created_at, data.room_id, data.user_id, data.body
+                catch err
+                  console.log(err)

         response.on "end", ->
           console.log "Streaming Connection closed. :("


Fixed bug where starter move stayed in availableMoves

--- src/js/screens/oak.coffee
+++ src/js/screens/oak.coffee
@@ -102,6 +102,7 @@
         @game.dialog.arrMenu 'Choose a technique to learn:', @game.availableMoves, cb

       (move, cb) =>
+        @game.removeMove(move.name)
         @game.player.addMove(move)
         @game.opponent.say move.name + '? I can definitely explain that... (one hour later)...
                            THERE! All set! Now it\'s off to debate. You\'ll begin in the novice


Remove unnecessary console.log

Remove unnecessary console.log

--- src/ks/views/intro.coffee
+++ src/ks/views/intro.coffee
@@ -39,8 +39,6 @@

   openKitchenSink: ->

-    console.log 'Open the kitchen sink.'
-
     KitchenSink = require './main'

     kitchenSink = new KitchenSink


Improve blanker behaviour when displaying nested DialogViews.

--- lib/ST/DialogView.coffee
+++ lib/ST/DialogView.coffee
@@ -1,6 +1,8 @@
 #= require ST/View

 ST.class 'DialogView', 'View', ->
+  @_blankerCount = 0
+
   @initializer (args={}) ->
     @super()
     @_element.attr 'id', 'dialog'
@@ -62,6 +64,8 @@
       true

   @method 'showBlanker', ->
+    ST.DialogView._blankerCount++
+
     # Add blanker div if it doesn't already exist
     if $('#blanker').length < 1
       blanker = $('

')
@@ -69,7 +73,7 @@
       blanker.click (e) -> e.stopPropagation()
       $('body').append blanker
       blanker.bind 'touchstart touchmove touchend', (e) -> e.preventDefault()
-
+
       # Fade blanker in
       if $.browser.webkit
         blanker.css('height', $(document).height())
@@ -83,18 +87,20 @@
       $('#blanker').stop().css('opacity', 0.6)

   @method 'hideBlanker', ->
-    # Get blanker div
-    blanker = $ '#blanker'
-    if blanker.length > 0
-      # Fade blanker out
-      if $.browser.webkit
-        blanker.css('-webkit-transition', 'opacity 100ms linear')
-          .css('opacity', 0.0)
-          .bind 'webkitTransitionEnd', ->
-            $(this).unbind('webkitTransitionEnd')
-            blanker.remove()
-      else
-        blanker.animate {opacity : 0}, 300, 'linear', -> blanker.remove()
+    ST.DialogView._blankerCount--
+    if ST.DialogView._blankerCount <= 0
+      # Get blanker div
+      blanker = $ '#blanker'
+      if blanker.length > 0
+        # Fade blanker out
+        if $.browser.webkit
+          blanker.css('-webkit-transition', 'opacity 100ms linear')
+            .css('opacity', 0.0)
+            .bind 'webkitTransitionEnd', ->
+              $(this).unbind('webkitTransitionEnd')
+              blanker.remove()
+        else
+          blanker.animate {opacity : 0}, 300, 'linear', -> blanker.remove()

   @method 'showDialog', ->
     self = this


Tweak

Tweak

--- src/lib/json_utils.coffee
+++ src/lib/json_utils.coffee
@@ -61,8 +61,7 @@
   #
   @parseQuery: (query) ->
     json = {}
-    for key, value of query
-      json[key] = JSONUtils.parseQueryValue(value)
+    json[key] = JSONUtils.parseQueryValue(value) for key, value of query
     return json

   @parseQueryValue: (value) ->


Fixed serializer pipeline preparations.

Fixed serializer pipeline preparations.

--- src/lib/serializer.coffee
+++ src/lib/serializer.coffee
@@ -203,6 +203,9 @@
     else
       @_outgoing  = shiftable

+    # Run the outgoing values through field pipelines before we enter the write
+    # loop. We need to skip over the blank fields and constants. We also skip
+    # over bit packed feilds because we do not apply pipelines to packed fields.
     skip = 0
     j = 0
     for value, i in @_outgoing
@@ -215,7 +218,10 @@
             skip++
         if skip > 0
           skip--
-      else if pattern.endianness isnt "x"
+      else
+        j++ while pattern[j] and pattern[j].endianness is "x"
+        if not pattern[j]
+          throw new Error "too many fields"
         @_outgoing[i] = @pipeline(pattern[j], value)
       j++


try to make an array from args, validate they're functions

--- lib/chain-builder.coffee
+++ lib/chain-builder.coffee
@@ -62,7 +62,14 @@

 class Chain extends require('events').EventEmitter

-  constructor: (options) -> @array = options?.array ? []
+  constructor: (options) ->
+    array =
+      if Array.isArray options then options
+      else if typeof options is 'function' then [ options ]
+      else if Array.isArray options?.array then options.array
+      else if not options? then []
+
+    @array = array

   add: (fns...) ->
     if Array.isArray fns?[0] then fns = fns[0] # unwrap array
@@ -103,7 +110,18 @@

 module.exports = (options) ->
-  if Array.isArray options then options = array:options
-  return new Chain options
+  array =
+    if Array.isArray options then options
+    else if typeof options is 'function' then [ options ]
+    else if Array.isArray options?.array then options.array
+    else []
+
+  # validate array's contents: must be functions
+  for element,index in array
+    unless 'function' is typeof(element)
+      return error:'Elements must be functions', element:element, index:index
+
+  new Chain array:array
+
 module.exports.Chain = Chain
 module.exports.Control = Control


Show the port name instead of undefined

Show the port name instead of undefined

--- lib/commandstream.coffee
+++ lib/commandstream.coffee
@@ -249,7 +249,7 @@
     tgtComponent = componentMap[tgtNode]
     tgtPort = componentLib.inputPort(tgtComponent, payload.tgt.port).id
   catch err
-    throw new Error "Could not attach IIP: '#{data} -> #{tgtPort} #{tgtNode} : #{err}"
+    throw new Error "Could not attach IIP: '#{data} -> #{payload.tgt.port} #{tgtNode} : #{err}"
   cmdBuf = dataLiteralToCommand(data, nodeMap[tgtNode].id, tgtPort)
   index += writeCmd buffer, index, cmdBuf
   return index


Make appendHTML work with script tags

Make appendHTML work with script tags

--- src/marbles/dom.js.coffee
+++ src/marbles/dom.js.coffee
@@ -64,6 +64,17 @@
   appendHTML: (el, html) ->
     tmp_el = document.createElement('div')
     tmp_el.innerHTML = html
+
+    # Hack to make appending script tags work as expected
+    for script_node in DOM.querySelectorAll('script', tmp_el)
+      new_node = document.createElement('script')
+      new_node.type = script_node.type
+      if script_node.src
+        new_node.src = script_node.src
+      else
+        new_node.innerHTML = script_node.innerHTML
+      DOM.replaceWith(script_node, new_node)
+
     for node in tmp_el.childNodes
       continue unless node
       el.appendChild(node)


Introduce log rotation.

Introduce log rotation.

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -11,7 +11,7 @@
 logger = new Logger
   streams: [
     { level: 'warn', stream: process.stderr }
-    { level: 'warn', path: "#{logDir}/sphere-order-distribution-#{argv.projectKey}.log" }
+    { level: 'warn', type: 'rotating-file', period: '1d', count: 90, path: "#{logDir}/sphere-order-distribution-#{argv.projectKey}.log" }
   ]

 options =


class ArgumentError extends Error

class ArgumentError extends Error

--- src/mix.it/helpers.coffee
+++ src/mix.it/helpers.coffee
@@ -12,5 +12,7 @@
     else if typeof mixin != 'object'
       throw new TypeError("Expected object, got something else")

+  ArgumentError: class ArgumentError extends Error
+

 module.exports = HELPERS


Initial drag allows dropping on gutter

Initial drag allows dropping on gutter

--- src/controller.coffee
+++ src/controller.coffee
@@ -1011,7 +1011,7 @@
         head = head.next

       if head is @tree.end and
-          @mainCanvas.width + @scrollOffsets.main.x > mainPoint.x > @scrollOffsets.main.x and
+          @mainCanvas.width + @scrollOffsets.main.x > mainPoint.x > @scrollOffsets.main.x - @gutter.offsetWidth and
           @mainCanvas.height + @scrollOffsets.main.y > mainPoint.y > @scrollOffsets.main.y
         @view.getViewNodeFor(@tree).highlightArea.draw @highlightCtx
         @lastHighlight = @tree


Cleanup

Cleanup

--- src/podroller/core.coffee
+++ src/podroller/core.coffee
@@ -199,11 +199,11 @@

             # send out headers
             headers =
-                "Content-Type":         "audio/mpeg",
-                "Connection":           "close",
-                "Transfer-Encoding":    "identity",
-                "Content-Length":       length,
-                "Accept-Ranges":        "bytes"
+                "Content-Type"      : "audio/mpeg",
+                "Connection"        : "close",
+                "Transfer-Encoding" : "identity",
+                "Content-Length"    : length,
+                "Accept-Ranges"     : "bytes"

             if rangeRequest
                 headers["Cache-Control"] = "no-cache"
@@ -262,8 +262,10 @@
         count = @_counter++

         # short-circuit if we're missing any options
-        console.log "preroller opts is ", @options.preroll, key
-        unless @options.preroll?.server && @options.preroll?.key && @options.preroll?.path
+        console.debug "preroller opts is ", @options.preroll, key
+        unless @options.preroll?.server &&
+        @options.preroll?.key &&
+        @options.preroll?.path
             cb?()
             return true

@@ -271,8 +273,12 @@
         query = qs.stringify(req.query)

         opts =
-            host:       @options.preroll.server
-            path:       [@options.preroll.path,@options.preroll.key,key,"?"+query].join("/")
+            host: @options.preroll.server
+            path: [
+                @options.preroll.path,
+                @options.preroll.key,
+                key, "?" + query
+            ].join("/")

         conn = req.connection

@@ -285,8 +291,8 @@

                 rres.on "data", (chunk) =>
                     buf = new Buffer(pre_data.length + chunk.length)
-                    pre_data.copy(buf,0)
-                    chunk.copy(buf,pre_data.length)
+                    pre_data.copy(buf, 0)
+                    chunk.copy(buf, pre_data.length)
                     pre_data = buf

                 # when preroll is done, call the output's callback


Remove keybinding cruft, make Ctrl-K work again

--- keymaps/megafinder.cson
+++ keymaps/megafinder.cson
@@ -1,12 +1,3 @@
-# Keybindings require three things to be fully defined: A selector that is
-# matched against the focused element, the keystroke and the command to
-# execute.
-#
-# Below is a basic keybinding which registers on all platforms by applying to
-# the root workspace element.
-
-# For more detailed documentation see
-# https://atom.io/docs/latest/advanced/keymaps
 '.platform-darwin atom-workspace':
   'cmd-p': 'megafinder:toggle'

@@ -23,10 +14,6 @@
   'ctrl-p': 'megafinder:move-cursor-up'
   'ctrl-n': 'megafinder:move-cursor-down'

-  # Alternative movement keys
-  'ctrl-i': 'megafinder:move-cursor-up'
-  'ctrl-k': 'megafinder:move-cursor-down'
-
   # A more powerful version of alt-backspace
   'ctrl-l': 'megafinder:delete-path-component'


fixed bug in table.groupby.  forgot to pass alias into partition() call

--- src/data/table.coffee
+++ src/data/table.coffee
@@ -333,7 +333,7 @@

   groupby: (cols, aggs, complete=no) ->
     new data.ops.Aggregate(
-      @partition(cols, complete),
+      @partition(cols, 'table', aggs, complete),
       aggs)

   partitionJoin: (table, cols, type="outer") ->


fix datepicker restriction

fix datepicker restriction

--- src/datepicker.coffee
+++ src/datepicker.coffee
@@ -1,7 +1,7 @@
 angular
 .module('angular-w')
 .directive('wDatepicker', ['wPopupManager', (popupManager)->
-    restrict: 'E'
+    restrict: 'EA'
     require: '?ngModel'
     scope: {}
     templateUrl: '/templates/datepicker.html'
@@ -12,4 +12,4 @@
         scope.date = ngModel.$modelValue
       scope.dateSelection = ->
         ngModel.$setViewValue(scope.date)
-  ])+  ])


del depr script

del depr script

--- source/components/popup/popup.jquery.coffee
+++ source/components/popup/popup.jquery.coffee
@@ -1,45 +0,0 @@
-
-hPopup = do ->
-    init = ->
-        $('body').on 'click', '.js-popup', openPopup
-
-    overflow = ->
-        outer = $('html')
-        widthInner = outer.width()
-        outer.addClass 'c-popup__outer'
-        widthOuter = outer.width()
-        width = widthOuter - widthInner + 'px'
-        outer.css 'margin-right', width
-
-    openPopup = ->
-        overflow()
-        el = $($(this).attr('data-popup'))
-        content = el.html()
-        $('body').prepend '
' +
-            '

' +
-            '

' +
-                '

' +
-                content +
-            '

'
-        $('body').off 'click', '.js-popup', openPopup
-
-        setTimeout (->
-            $('.c-popup').addClass 'c-popup_show'
-        ), 100
-        $('body').on 'click', '.c-popup__shadow, .c-popup__close', closePopup
-
-    closePopup = ->
-        $('body').off 'click', '.c-popup__shadow,
-                                .c-popup__close', closePopup
-        $('.c-popup_show').removeClass 'c-popup_show'
-        setTimeout (->
-            $('html').removeClass('c-popup__outer').removeAttr 'style'
-            $('.c-popup').remove()
-            $('body').on 'click', '.js-popup', openPopup
-        ), 300
-
-    {
-        init: init # Start initialize module
-    }
-
-hPopup.init()


Reduce number of AJAX calls in variant autocomplete.

Fixes #6338
Fixes #5667

--- backend/app/assets/javascripts/spree/backend/variant_autocomplete.js.coffee
+++ backend/app/assets/javascripts/spree/backend/variant_autocomplete.js.coffee
@@ -19,6 +19,7 @@
         callback data
     ajax:
       url: Spree.url(Spree.routes.variants_api)
+      quietMillis: 200
       datatype: "json"
       data: (term, page) ->
         q:


roles based course items

roles based course items

--- tutor/src/components/course-listing/index.cjsx
+++ tutor/src/components/course-listing/index.cjsx
@@ -42,16 +42,28 @@
     <Item course={course} />
   </BS.Col>

+DEFAULT_COURSE_ITEMS =
+  teacher: Course
+  student: Course
+
 CourseListingBase = React.createClass
   displayName: 'CourseListingBase'
+
+  getItems: ->
+    _.merge({}, DEFAULT_COURSE_ITEMS, @props.Items)
+
   render: ->
-    {courses, Item, className} = @props
+    {courses, className} = @props
+    Items = @getItems()
+
     sectionClasses = classnames('course-listing-section', className)

     <BS.Row className={sectionClasses}>
-      {_.map(courses, _.partial(wrapCourseItem, Item))}
+      {_.map(courses, (course) ->
+        Item = Items[CurrentUserStore.getCourseRole(course.id)]
+        if Item then wrapCourseItem(Item, course)
+      )}
     </BS.Row>
-

 AddCourseArea = ->

Add a course

@@ -93,7 +105,6 @@
         <CourseListingBase
           className="#{baseName}-section"
           courses={courses}
-          Item={Course}
         />}
       <@AddCourses />
     </BS.Grid>
@@ -125,7 +136,7 @@
           <CourseListingBase
             className="#{baseName}-section"
             courses={courses}
-            Item={CoursePastTeacher}
+            Items={{teacher: CoursePastTeacher}}
             key="#{baseName}-section"/>
         ]}
     </BS.Grid>


Only finish responses if response was sent

--- src/scripts/access.coffee
+++ src/scripts/access.coffee
@@ -58,4 +58,4 @@
       res.send "_#{res.random strings.unknowncommand}
                 #{res.random strings.askforhelp}_"
       Logger.addReaction 'question', res.message
-    res.finish()
+      res.finish()


Use absolute path when calling Git.isPathIgnored

--- src/app/project.coffee
+++ src/app/project.coffee
@@ -96,7 +96,7 @@
     @ignoreRepositoryPath(path)

   ignoreRepositoryPath: (path) ->
-    @hideIgnoredFiles and @repo.isPathIgnored(path)
+    @hideIgnoredFiles and @repo.isPathIgnored(fs.join(@getPath(), path))

   ignorePathRegex: ->
     @ignoredPathRegexes.map((regex) -> "(#{regex.source})").join("|")


Removed 404 images and added replacements to beerme.

--- src/scripts/beerme.coffee
+++ src/scripts/beerme.coffee
@@ -16,9 +16,7 @@
 beers = [
   "http://organicxbenefits.com/wp-content/uploads/2011/11/organic-beer-health-benefits.jpg",
   "http://www.beer100.com/images/beermug.jpg",
-  "http://www.joyandfood.com/wp-content/uploads/2012/05/Beer-beer.jpg",
   "http://www.bristolvantage.com/wp-content/uploads/2012/02/beer-calories1.jpg",
-  "http://cdn.biruwananbai.com/wp-content/uploads/2012/04/more_beer-01.jpg",
   "http://blog.collegebars.net/uploads/10-beers-you-must-drink-this-summer/10-beers-you-must-drink-this-summer-sam-adams-summer-ale.jpg",
   "http://media.treehugger.com/assets/images/2011/10/save-the-beers.jpg",
   "http://poemsforkush.files.wordpress.com/2012/04/beer.jpg",
@@ -27,10 +25,12 @@
   "http://images.free-extras.com/pics/f/free_beer-911.jpg",
   "http://images.seroundtable.com/android-beer-dispenser-1335181876.jpg",
   "http://www.mediabistro.com/fishbowlDC/files/original/beer-will-change-the-world.jpg",
-  "http://dribbble.s3.amazonaws.com/users/79978/screenshots/594281/attachments/47191/more.png",
   "http://www.gqindia.com/sites/default/files/imagecache/article-inner-image-341-354/article/slideshow/1289/beer.JPG",
   "http://www.gqindia.com/sites/default/files/imagecache/article-inner-image-341-354/article/slideshow/1289/beer2.jpg",
-  "http://www.gqindia.com/sites/default/files/imagecache/article-inner-image-341-354/article/slideshow/1289/Beer3.jpg"
+  "http://www.gqindia.com/sites/default/files/imagecache/article-inner-image-341-354/article/slideshow/1289/Beer3.jpg",
+  "http://365thingsaustin.com/wp-content/uploads/beer-flight1.jpg",
+  "http://i102.photobucket.com/albums/m109/beanbone/Austin_Amber_200.jpg",
+  "http://media.culturemap.com/crop/c8/a3/600x450/beer_tasting.jpg"
 ]

 module.exports = (robot) ->


Freshen up the haters script

Freshen up the haters script

--- src/scripts/haters.coffee
+++ src/scripts/haters.coffee
@@ -14,9 +14,7 @@
 #   atmos

 haters = [
-  "http://www.hatersgoingtohate.com/wp-content/uploads/2010/06/haters-gonna-hate-rubberband-ball.jpg"
-, "http://www.hatersgoingtohate.com/wp-content/uploads/2010/06/haters-gonna-hate-cat.jpg"
-, "http://jesad.com/img/life/haters-gonna-hate/haters-gonna-hate01.jpg"
+  "http://jesad.com/img/life/haters-gonna-hate/haters-gonna-hate01.jpg"
 , "http://i671.photobucket.com/albums/vv78/Sinsei55/HatersGonnaHatePanda.jpg"
 , "http://24.media.tumblr.com/tumblr_lltwmdVpoL1qekprfo1_500.gif"
 , "http://s3.amazonaws.com/kym-assets/photos/images/newsfeed/000/087/536/1292102239519.gif"
@@ -24,6 +22,9 @@
 , "http://c.static.memegenerator.net/cache/instances/500x/13/13355/13676320.jpg"
 , "http://icanhasinternets.com/wp-content/uploads/2010/05/haters.gif"
 , "http://icanhasinternets.com/wp-content/uploads/2010/05/haters5.jpg"
+, "http://media.tumblr.com/tumblr_m2yv2hqw9l1rnvwt1.gif"
+, "http://loldailyfun.com/wp-content/uploads/2012/02/Haters-Gonna-hate-Funny.jpg"
+, "http://brian.io/slides/incog-intro/img/rollin.jpg"
 ]

 hatin = (msg) ->


Store regexs in variables

Store regexs in variables

--- src/scripts/loader.coffee
+++ src/scripts/loader.coffee
@@ -49,6 +49,8 @@
     download = /^\/(exports)\//
     external = /^((f|ht)tps?:)?\/\//
     resources = /\/(resources|exports)\//
+    mailto = /^mailto:(.+)/
+    exports = /exports\/([^\/:]+).(pdf|epub|zip)/

     # Catch internal application links and let Backbone handle the routing
     $(document).on 'click', 'a[href]:not([data-bypass]):not([href^="#"])', (e) ->
@@ -56,7 +58,7 @@
       href = $this.attr('href')

       # Only handle links intended to be processed by Backbone
-      if e.isDefaultPrevented() or href.charAt(0) is '#' or /^mailto:.+/.test(href) then return
+      if e.isDefaultPrevented() or href.charAt(0) is '#' or mailto.test(href) then return

       e.preventDefault()

@@ -72,7 +74,7 @@
           window.open(href, '_blank')
       else if download.test(href)
         if document.cookie.indexOf('donation') is -1
-          content = href.match(/exports\/([^\/:]+).(pdf|epub|zip)/)
+          content = href.match(exports)
           router.navigate("/donate/download/#{content[1]}/#{content[2]}", {trigger: true})
         else
           downloadUrl(href)


SMS notifications and presence added to notify

--- src/scripts/notify.coffee
+++ src/scripts/notify.coffee
@@ -10,31 +10,72 @@
 # Commands:
 #   hubot notify me by prowl with YOUR_PROWL_API_KEY
 #   hubot notify me by nma with YOUR_NMA_API_KEY
+#   hubot notify me by sms with 15556667890
 #   hubot list notifiers
 #
 # Author:
-#   marten
-
+#   marten, darvin
+#
+https = require "https"
 Prowl = require "prowler"
 QS = require "querystring"

 module.exports = (robot) ->
-  robot.hear /@(\w+)/i, (msg) ->
-    sender   = msg.message.user.name.toLowerCase()
-    username = msg.match[1].toLowerCase()
+  sendSms = (to, msg) ->
+    sid   = process.env.HUBOT_SMS_SID
+    tkn   = process.env.HUBOT_SMS_TOKEN
+    from  = process.env.HUBOT_SMS_FROM
+    auth  = 'Basic ' + new Buffer(sid + ':' + tkn).toString("base64")
+    data  = QS.stringify
+      From: from
+      To: to
+      Body: "#{msg.user.name}: #{msg.message.text}"
+    unless sid
+      msg.send "Twilio SID isn't set."
+      msg.send "Please set the HUBOT_SMS_SID environment variable."
+      return
+
+    unless tkn
+      msg.send "Twilio token isn't set."
+      msg.send "Please set the HUBOT_SMS_TOKEN environment variable."
+      return
+
+    unless from
+      msg.send "Twilio from number isn't set."
+      msg.send "Please set the HUBOT_SMS_FROM environment variable."
+      return
+
+    msg.http("https://api.twilio.com")
+      .path("/2010-04-01/Accounts/#{sid}/SMS/Messages.json")
+      .header("Authorization", auth)
+      .header("Content-Type", "application/x-www-form-urlencoded")
+      .post(data) (err, res, body) ->
+        json = JSON.parse body
+        switch res.statusCode
+          when 201
+            msg.send "Sent sms to #{to}"
+          when 400
+            msg.send "Failed to send. #{json.message}"
+          else
+            msg.send "Failed to send."
+
+
+
+  notify = (username, msg) ->
     notifies = []
+    console.error "Going notify #{username}"

     if username == "all" or username == "everyone"
       for username, apikey of robot.brain.data.notifiers
-        unless username == sender
+        unless username.toLowerCase() == msg.message.user.name.toLowerCase()
           notifies.push apikey
-    else if apikey = robot.brain.data.notifiers[username]
+    else if apikey = robot.brain.data.notifiers[username.toLowerCase()]
       notifies.push apikey

     for notifier in notifies
       [protocol, apikey...] = notifier.split(':')
       apikey = apikey.join('')
-      #msg.send("Notified #{protocol} by #{apikey}")
+      msg.send("Notified #{protocol} by #{apikey}")

       switch protocol
         when "prowl"
@@ -43,8 +84,12 @@
             application: 'RoQua Hubot'
             event: 'Mention'
             description: msg.message.text
+        when "sms"
+          console.error "Sending sms"
+          sendSms apikey, msg
+
         when "nma"
-          params =
+          params =
             apikey: apikey
             application: "Hubot"
             event: "Mention"
@@ -54,6 +99,51 @@
             .get() (err, res, body) ->
               body

+  checkIfOffline = (user, callback) ->
+    if process.env.HUBOT_HIPCHAT_TOKEN
+      options =
+        host: "api.hipchat.com"
+        port: 443
+        path: "/v1/users/show?"+QS.stringify({
+          format:"json"
+          user_id:user.id
+          auth_token:process.env.HUBOT_HIPCHAT_TOKEN
+        })
+        method: "GET"
+
+      req = https.request options, (res) ->
+        res.on "data", (data) ->
+          userData = JSON.parse(data.toString())
+          callback null, userData.user.status=="offline"
+        res.on "error", (err) ->
+          callback err
+      req.end()
+    else
+      callback null, false
+
+  robot.hear /@(\w+)/i, (msg) ->
+    sender   = msg.message.user.name.toLowerCase()
+    mentionedUserName = msg.match[1].toLowerCase()
+    if mentionedUserName == "all" or mentionedUserName == "everyone"
+      notify "all", msg
+      msg.send "All notified!"
+      return
+
+
+    for userId, user of robot.brain.data.users
+      if user.mention_name==mentionedUserName
+        username=user.name
+        mentionedUserId = userId
+        theUser = user
+    if not mentionedUserId?
+      return
+    checkIfOffline theUser, (err, offline) ->
+      if err
+        throw err
+      else if offline
+        notify username, msg
+
+

   robot.respond /notify me by prowl with (\w+)/i, (msg) ->
     apikey = msg.match[1].toLowerCase()
@@ -67,6 +157,13 @@
     robot.brain.data.notifiers[msg.message.user.name.toLowerCase()] = "nma:#{apikey}"
     msg.send "OK"

+  robot.respond /notify me by sms with (\d+)/i, (msg) ->
+    number = msg.match[1].toLowerCase()
+    robot.brain.data.notifiers ?= {}
+    robot.brain.data.notifiers[msg.message.user.name.toLowerCase()] = "sms:#{number}"
+    msg.send "OK"
+
+
   robot.respond /list notifiers/i, (msg) ->
     for username, apikey of robot.brain.data.notifiers
       msg.send("I notify #{username} with #{apikey}")


Logging when we add a new build to the hash

--- src/scripts/sifter.coffee
+++ src/scripts/sifter.coffee
@@ -136,5 +136,6 @@
                 build = issue.subject.replace change_request_regex, ""
                 client.sismember "qa_builds", build, (error, reply) ->
                   if reply is 0
+                    console.log "Adding #{build} to qa_builds hash"
                     client.sadd "qa_builds", build, (error, reply) ->
                       msg.send "#{build} has just been deployed to QA"


Add onAnimationHalt

Add onAnimationHalt

--- framer/Animation.coffee
+++ framer/Animation.coffee
@@ -318,6 +318,7 @@
 	## EVENT HELPERS

 	onAnimationStart: (cb) -> @on(Events.AnimationStart, cb)
+	onAnimationHalt: (cb) -> @on(Events.AnimationHalt, cb)
 	onAnimationStop: (cb) -> @on(Events.AnimationStop, cb)
 	onAnimationEnd: (cb) -> @on(Events.AnimationEnd, cb)
 	onAnimationDidStart: (cb) -> @on(Events.AnimationDidStart, cb)


no effect on tempory data

no effect on tempory data

--- atmospheric/002-accumulated-heat/csmain.coffee
+++ atmospheric/002-accumulated-heat/csmain.coffee
@@ -20,6 +20,7 @@
     $ = (selector) -> bonzo(qwery(selector))

     domReady ->
+        tmp = null
         map = null
         frame = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]

@@ -28,8 +29,9 @@
             data = event.data
             if data.msg
                 $("#msg").html(data.msg)
+                map = tmp
             else if data.trn
-                map = data.trn
+                tmp = data.trn

         invoke = ->
             worker.postMessage('start')


Terminal: AppController: remove vmName and vmController dependency

--- client/Terminal/AppController.coffee
+++ client/Terminal/AppController.coffee
@@ -29,11 +29,10 @@
   constructor:(options = {}, data)->

     params              = options.params or {}
-    vmName              = params.vmName  or KD.getSingleton("vmController").defaultVmName
-    options.view        = (new WebTermAppView { vmName })
+    options.view        = new WebTermAppView()
                             .on 'command', @bound 'handleCommand'
     options.appInfo     =
-      title             : "Terminal on #{vmName}"
+      title             : "Terminal"
       cssClass          : "webterm"

     super options, data


Tests: IDE: Workspace: Added switchAnotherWorkspace test.

--- client/test/lib/ide/workspace.coffee
+++ client/test/lib/ide/workspace.coffee
@@ -52,3 +52,25 @@
           .waitForElementVisible  '.vm-info', 20000
           .assert.containsText    '.vm-info', name # Assertion
           .end()
+
+
+  switchAnotherWorkspace: (browser, workspaceName) ->
+
+    helpers.beginTest(browser)
+    helpers.waitForVMRunning(browser)
+    workspaceName1 = helpers.createWorkspace(browser)
+    workspaceName2 = helpers.createWorkspace(browser)
+    vmName         = 'koding-vm-0'
+    workspaceLink1 = 'a[href="/IDE/' + vmName + '/' + workspaceName1 + '"]'
+    workspaceLink2 = 'a[href="/IDE/' + vmName + '/' + workspaceName2 + '"]'
+
+    browser
+      .waitForElementPresent  workspaceLink1, 20000 # Assertion
+      .click                  workspaceLink1
+      .pause                  300
+      .assert.containsText    '.vm-info', '~/Workspaces/' + workspaceName1 # Assertion
+      .waitForElementPresent  workspaceLink2, 20000 # Assertion
+      .click                  workspaceLink2
+      .pause                  300
+      .assert.containsText    '.vm-info', '~/Workspaces/' + workspaceName2 # Assertion
+      .end()


add a MESG_QUEUE_MAX_WARN param

add a MESG_QUEUE_MAX_WARN param

--- src/smc-hub/client.coffee
+++ src/smc-hub/client.coffee
@@ -39,6 +39,7 @@
 # If a client sends a massive burst of messages, we discard all but the most recent this many of them:
 #MESG_QUEUE_MAX_COUNT    = 25
 MESG_QUEUE_MAX_COUNT    = 300
+MESG_QUEUE_MAX_WARN    = 50
 # Any messages larger than this is dropped (it could take a long time to handle, by a de-JSON'ing attack, etc.).
 MESG_QUEUE_MAX_SIZE_MB  = 10

@@ -500,6 +501,9 @@
                 # done doing all tasks
                 delete @_handle_data_queue_empty_function
                 return
+
+            if @_handle_data_queue.length > MESG_QUEUE_MAX_WARN
+                dbg("MESG_QUEUE_MAX_WARN(=#{MESG_QUEUE_MAX_WARN}) exceeded (=#{@_handle_data_queue.length}) -- just a warning")

             # drop oldest message to keep
             if @_handle_data_queue.length > MESG_QUEUE_MAX_COUNT


Remove name key from generated config

Remove name key from generated config

--- src/commandline.coffee
+++ src/commandline.coffee
@@ -249,7 +249,7 @@
     process.exit(0)
 else if options.argv.makeconfig
     console.log JSON.stringify coffeelint.getRules(),
-        ((k,v) -> v unless k in ['message', 'description']), 4
+        ((k,v) -> v unless k in ['message', 'description', 'name']), 4
 else if options.argv._.length < 1 and not options.argv.s
     options.showHelp()
     process.exit(1)


Fixed js error in some case.

Fixed js error in some case.

--- vendor/assets/javascripts/simple_pusher.coffee
+++ vendor/assets/javascripts/simple_pusher.coffee
@@ -6,7 +6,7 @@
       data = e.data.split(":")
       event = data[0]
       message = data[1..-1].join(":")
-      callback_fn.call(this, message) for callback_fn in @_callbacks[event]
+      callback_fn.call(this, message) for callback_fn in @_callbacks[event] if @_callbacks[event]

   on: (event_name, callback_fn) ->
     @_callbacks[event_name] = (@_callbacks[event_name] ||= [])


Outdent line previously wrapped in try/catch

--- src/stdlib/require.coffee
+++ src/stdlib/require.coffee
@@ -63,9 +63,9 @@
       compiled = CoffeeScript.compile(__read(file), filename: file)
       writeToCache = true

-      evaluated = exts.js(file, compiled)
-      $native.write(cacheFilePath, compiled) if writeToCache
-      evaluated
+    evaluated = exts.js(file, compiled)
+    $native.write(cacheFilePath, compiled) if writeToCache
+    evaluated

 getPath = (path) ->
   path = resolve(path)


Fix clearInterval

Fix clearInterval

--- widgets/calendar_events/calendar_events.coffee
+++ widgets/calendar_events/calendar_events.coffee
@@ -1,7 +1,4 @@
 class Dashing.CalendarEvents extends Dashing.Widget
-
-  if intervalId?
-    clearInterval(intervalId)

   MONTHS = [
     'Janeiro',
@@ -19,6 +16,9 @@
   ]

   onData: (data) =>
+    if intervalId?
+      clearInterval(intervalId)
+
     event = rest = null
     getEvents = (first, others...) ->
       event = first


MINOR inherited Page_Template is now PageTemplate - as per naming regulations

--- superphy/src/app/mithril/coffee/Pages/Factors.coffee
+++ superphy/src/app/mithril/coffee/Pages/Factors.coffee
@@ -1,4 +1,4 @@
-class Factors extends Page_Template
+class Factors extends PageTemplate
     #a list to be passed to the link generator function
     model: () =>
         @value = m.prop('')


Do not assume `errorObject` is available.

--- lib.coffee
+++ lib.coffee
@@ -167,7 +167,7 @@
               @_internal.test.fail
                 type: 'uncaught_exception'
                 message: errorMessage
-                stack: errorObject.stack
+                stack: errorObject?.stack

           # Ensure that the test failed if it was registered as a failing test.
           if @_internal.options.mustFail


Properly dispose of views

Properly dispose of views

--- src/scripts/helpers/backbone/views/base.coffee
+++ src/scripts/helpers/backbone/views/base.coffee
@@ -4,7 +4,9 @@
   Backbone = require('backbone')
   settings = require('cs!settings')

-  cleanObject = (obj) ->
+  dispose = (obj) ->
+    obj?.__proto__ = null
+    obj?.constructor?.prototype = null
     delete obj[key] for key of obj

   class Region
@@ -36,7 +38,7 @@

     close: () ->
       @empty()
-      cleanObject()
+      dispose()

   class Regions
     constructor: (regions = {}, $context) ->
@@ -50,14 +52,16 @@
     initialize: () ->
       @regions = new Regions(@regions, @)

-    _renderDom: (data) ->
-      @$el?.html(@template?(data) or @template)
+    renderDom: () ->
+      @$el?.html(@getTemplate())

     # Update page title
     updateTitle: () ->
       document.title = settings.titlePrefix + @pageTitle if @pageTitle

-    _render: () ->
+    getTemplate: () -> @template?(@getTemplateData()) or @template
+
+    getTemplateData: () ->
       data = @model?.toJSON() or @collection?.toJSON() or {}

       if typeof @templateHelpers is 'function'
@@ -70,8 +74,11 @@
           else
             data[key] = value

+      return data
+
+    _render: () ->
       @updateTitle()
-      @_renderDom(data)
+      @renderDom()

     render: () ->
       @onBeforeRender()
@@ -93,7 +100,8 @@
       _.each @regions, (region) ->
         region.close()

+      @off() # Remove all event listeners
       @remove()
       @unbind()
-      cleanObject()
+      dispose(@)
       return @


Client: UserProfile: Updated confirmCurrentPassword method to hide loader from `save changes` button when VerifyPasswordModal visible

--- client/account/lib/views/accounteditusername.coffee
+++ client/account/lib/views/accounteditusername.coffee
@@ -305,7 +305,8 @@
         return callback 'Current password cannot be confirmed'  unless confirmed

         callback null
-    modal.once 'ModalCancelled', @bound 'hideSaveButtonLoader'
+
+    @hideSaveButtonLoader()

   viewAppended: ->


refactor: Changed KD.singletons to KD.getSingleton.

--- client/landing/app/MainApp/paymentcontroller.coffee
+++ client/landing/app/MainApp/paymentcontroller.coffee
@@ -238,13 +238,13 @@
         return new KDNotificationView
           title : err.message or "Something bad happened while creating VM"
       else
-        KD.singletons.finderController.mountVm vm.name
+        KD.getSingleton("finderController").mountVm vm.name
         vmController.emit 'VMListChanged'
       paymentModal?.destroy()

     planCode = plan.code
     if type is 'group' or type is 'expensed'
-      group = KD.singletons.groupsController.getCurrentGroup()
+      group = KD.getSingleton("groupsController").getCurrentGroup()
       group.checkPayment (err, payments)->
         if err or payments.length is 0


Make multiple subscriptions optional.

Make multiple subscriptions optional.

--- client/landing/app/MainApp/paymentcontroller.coffee
+++ client/landing/app/MainApp/paymentcontroller.coffee
@@ -414,12 +414,13 @@
     else
       if type is 'group'
         group.makePayment
-          plan: plan.code
+          plan     : plan.code
+          multiple : yes
         , (err, result)->
           unless err
             vmController.createGroupVM type, plan.code
       else
-        plan.subscribe {}, (err, result)->
+        plan.subscribe {multiple: yes}, (err, result)->
           unless err
             vmController.createGroupVM type, plan.code


Work around issue causing google chat attachments to not appear correctly.

--- src/ui/models/conv.coffee
+++ src/ui/models/conv.coffee
@@ -25,30 +25,50 @@
                     break

         if url?
-            # Remove the 'I shared ...' message, newline and link
-            if index - 2 >= 0
-                cont.segment.splice(index - 2, 3)
-
-            thumburl = new URL(url.toString())
-            thumburl.searchParams.set 'url_type', 'FIFE_URL'
-            thumburl.searchParams.set 'sz', 's512'
-
-            embed.type_[0] = 249
-            embed.data = "and0"
-            embed.plus_photo = {}
-            embed.plus_photo.data = {}
-            embed.plus_photo.data.thumbnail = {}
-            embed.plus_photo.data.thumbnail.image_url = url.toString()
-            embed.plus_photo.data.thumbnail.thumb_url = thumburl.toString()
-            embed.plus_photo.data.original_content_url = null
-            embed.plus_photo.data.media_type = 'MEDIA_TYPE_PHOTO'
-
-            if url.searchParams.get('url_type') is 'STREAMING_URL'
-                embed.plus_photo.data.media_type = 'MEDIA_TYPE_VIDEO'
-                embed.plus_photo.videoinformation = {}
-                embed.plus_photo.videoinformation.thumb = thumburl.toString()
-                embed.plus_photo.videoinformation.url = url.toString()
-                embed.plus_photo.videoinformation.public = false
+            params = url.searchParams
+            type = params.get('url_type')
+
+            isPhoto = type is 'FIFE_URL' and (params.get('width')? or params.get('height')?)
+            isVideo = type is 'STREAMING_URL'
+            if isPhoto or isVideo
+                # Remove the 'I shared ...' message, newline and link
+                if index - 2 >= 0
+                    cont.segment.splice(index - 2, 3)
+
+                thumburl = new URL(url.toString())
+                thumburl.searchParams.set 'url_type', 'FIFE_URL'
+                thumburl.searchParams.set 'sz', 's512'
+
+                embed.type_[0] = 249
+                embed.data = "and0"
+                embed.plus_photo = {}
+                embed.plus_photo.data = {}
+                embed.plus_photo.data.thumbnail = {}
+                embed.plus_photo.data.thumbnail.image_url = url.toString()
+                embed.plus_photo.data.thumbnail.thumb_url = thumburl.toString()
+                embed.plus_photo.data.original_content_url = null
+                embed.plus_photo.data.media_type = if isPhoto 'MEDIA_TYPE_PHOTO' else 'MEDIA_TYPE_VIDEO'
+
+                if isVideo
+                    embed.plus_photo.videoinformation = {}
+                    embed.plus_photo.videoinformation.thumb = thumburl.toString()
+                    embed.plus_photo.videoinformation.url = url.toString()
+                    embed.plus_photo.videoinformation.public = false
+            else
+                # Regular attachment, try to extract the name from the message
+                if index - 2 >= 0
+                    name = cont.segment[index - 2]?.text
+                    names = cont.segment[index - 2]?.text.match(/‘(.+?)’/)
+                    if names? and names.length > 1
+                        name = names[1]
+
+                    cont.segment[index].text = name
+                    cont.segment.splice(index - 2, 2)
+
+    # If we still have a 456 attachment here, unset it as it causes issues with later processing
+    if embed?.type_?[0] is 456
+        delete cont?.attachment
+
     msg

 add = (conv) ->


Remove old deprecated methods from 1.0

Remove old deprecated methods from 1.0

--- lib/autoprefixer.coffee
+++ lib/autoprefixer.coffee
@@ -39,17 +39,6 @@
   process: (str, options = {}) ->
     @processor().process(str, options)

-  # Old deprecated API from Autoprefixer 0.x
-  compile: (str, options = {}) ->
-    fixed = { }
-    for name, value of options
-      name = 'from' if name == 'file'
-      fixed[name] = value
-
-    console?.warn('autoprefixer: replace compile() to process(). ' +
-                  'Method compile() is deprecated and will be removed in 1.1.')
-    @process(str, fixed).css
-
   # Return PostCSS processor, which will add necessary prefixes
   postcss: (css) =>
     @prefixes.processor.remove(css)


Update server to work as standalone BO proxy without twitter credentials.

--- coffee/twitter_connector/twitter_auth_server.coffee
+++ coffee/twitter_connector/twitter_auth_server.coffee
@@ -10,30 +10,34 @@

 TWITTER_GET_OAUTH_TOKEN_URL = "https://api.twitter.com/oauth2/token"

-keys = require './twitter_keys.coffee'
+try
+    keys = require './twitter_keys.coffee'

-client = new Twitter
-  consumer_key: keys.CONSUMER_KEY,
-  consumer_secret: keys.CONSUMER_SECRET,
-  access_token_key: keys.ACCESS_TOKEN,
-  access_token_secret: keys.ACCESS_TOKEN_SECRET
+    client = new Twitter
+      consumer_key: keys.CONSUMER_KEY,
+      consumer_secret: keys.CONSUMER_SECRET,
+      access_token_key: keys.ACCESS_TOKEN,
+      access_token_secret: keys.ACCESS_TOKEN_SECRET
+
+    app.get '/search', (req, res)->
+      console.log "--> #{req.path} -- #{JSON.stringify(req.query)}"
+      #res.send("OK")
+      #return
+      client.get 'search/tweets', req.query, (error, tweets, response)->
+        if error
+          console.log "<=== Error: #{error}"
+          res.status(500).send("
Error: #{JSON.stringify(error)}
")
+
+        tweet_json = JSON.stringify(tweets)
+        console.log "<-- OK #{tweet_json.length} bytes"
+
+        res.setHeader('Content-Type', 'application/json')
+        res.send(tweet_json)
+catch ex
+    console.log "Failed to initialize twitter server, but it doesn't affect other functionalities.", ex
+

 app.get '/', (req, res)-> res.send("
Hello
")
-
-app.get '/search', (req, res)->
-  console.log "--> #{req.path} -- #{JSON.stringify(req.query)}"
-  #res.send("OK")
-  #return
-  client.get 'search/tweets', req.query, (error, tweets, response)->
-    if error
-      console.log "<=== Error: #{error}"
-      res.status(500).send("
Error: #{JSON.stringify(error)}
")
-
-    tweet_json = JSON.stringify(tweets)
-    console.log "<-- OK #{tweet_json.length} bytes"
-
-    res.setHeader('Content-Type', 'application/json')
-    res.send(tweet_json)

 app.get '/sap/tabledefinitions', (req, res) ->
     sap.getTables req.query.wsdl, req.query.credentials, (err, tables) ->


Translated 'nav' and 'play' sections (#5457)

--- app/locale/mk-MK.coffee
+++ app/locale/mk-MK.coffee
@@ -153,47 +153,47 @@
 #    student: "Student"

   nav:
-#    educators: "Educators"
-#    follow_us: "Follow Us"
-#    general: "General"
-#    map: "Map"
+    educators: "Едукатори"
+    follow_us: "Следи нè"
+    general: "Општо"
+    map: "Мапа"
     play: "Нивоа" # The top nav bar entry where players choose which levels to play
     community: "Заедница"
-#    courses: "Courses"
+    courses: "Курсеви"
     blog: "Блог"
     forum: "Форум"
     account: "Сметка"
-#    my_account: "My Account"
+    my_account: "Мој Профил"
     profile: "Профил"
     home: "Дома"
     contribute: "Допринеси"
     legal: "Законски"
-#    privacy: "Privacy Notice"
+    privacy: "Приватност"
     about: "За CodeCombat"
     contact: "Контакт"
     twitter_follow: "Следи"
-#    my_classrooms: "My Classes"
-#    my_courses: "My Courses"
-#    my_teachers: "My Teachers"
-#    careers: "Careers"
-#    facebook: "Facebook"
-#    twitter: "Twitter"
-#    create_a_class: "Create a Class"
-#    other: "Other"
-#    learn_to_code: "Learn to Code!"
-#    toggle_nav: "Toggle navigation"
-#    schools: "Schools"
-#    get_involved: "Get Involved"
-#    open_source: "Open source (GitHub)"
-#    support: "Support"
-#    faqs: "FAQs"
-#    copyright_prefix: "Copyright"
-#    copyright_suffix: "All Rights Reserved."
-#    help_pref: "Need help? Email"
-#    help_suff: "and we'll get in touch!"
-#    resource_hub: "Resource Hub"
-#    apcsp: "AP CS Principles"
-#    parent: "Parents"
+    my_classrooms: "Мои училници"
+    my_courses: "Мои курсеви"
+    my_teachers: "Мои наставници"
+    careers: "Кариера"
+    facebook: "Facebook"
+    twitter: "Twitter"
+    create_a_class: "Креирај училница"
+    other: "Друго"
+    learn_to_code: "Научи да кодираш!"
+    toggle_nav: "Промени го менито"
+    schools: "Школи"
+    get_involved: "Придружи се"
+    open_source: "Open source (GitHub)"
+    support: "Поддршка"
+    faqs: "Неодамна поставени прашања"
+    copyright_prefix: "Авторски права"
+    copyright_suffix: "Сите права задржани."
+    help_pref: "Треба помош? Прати и-мејл!"
+    help_suff: "и ќе бидеме во контакт!"
+    resource_hub: "Извор"
+    apcsp: "AP CS Принципи"
+    parent: "Родители"

   modal:
     close: "Затвори"
@@ -212,26 +212,26 @@
     subscribe_as_diplomat: "Зачлени се како Дипломат"

   play:
-#    title: "Play CodeCombat Levels - Learn Python, JavaScript, and HTML"
-#    meta_description: "Learn programming with a coding game for beginners. Learn Python or JavaScript as you solve mazes, make your own games, and level up. Challenge your friends in multiplayer arena levels!"
-#    level_title: "__level__ - Learn to Code in Python, JavaScript, HTML"
-#    video_title: "__video__ | Video Level"
-#    game_development_title: "__level__ | Game Development"
-#    web_development_title: "__level__ | Web Development"
-#    anon_signup_title_1: "CodeCombat has a"
-#    anon_signup_title_2: "Classroom Version!"
-#    anon_signup_enter_code: "Enter Class Code:"
-#    anon_signup_ask_teacher: "Don't have one? Ask your teacher!"
-#    anon_signup_create_class: "Want to create a class?"
-#    anon_signup_setup_class: "Set up a class, add your students, and monitor progress!"
-#    anon_signup_create_teacher: "Create free teacher account"
+    title: "Играј CodeCombat Нивоа - Научи Python, JavaScript и HTML"
+    meta_description: "Научи програмирање преку игра за кодирање наменета за почетници. Научи Python или JavaScript преку решавање загатки, креирање свои игри и појачување на херојот. Предизвикај ги своите пријатели во арена нивоата за повеќе играчи!"
+    level_title: "__level__ - Научи да кодираш во Python, JavaScript и HTML"
+    video_title: "__video__ | Video Level"
+    game_development_title: "__level__ | Развивање на игри"
+    web_development_title: "__level__ | Развивање на вебсајти"
+    anon_signup_title_1: "CodeCombat има"
+    anon_signup_title_2: "Верзија за училници!"
+    anon_signup_enter_code: "Внеси код на училница:"
+    anon_signup_ask_teacher: "Немаш код? Прашај го својот наставник!"
+    anon_signup_create_class: "Сакаш да креираш училница?"
+    anon_signup_setup_class: "Намести училница,додај ги своите ученици, и следи го напредокот!"
+    anon_signup_create_teacher: "Креирај бесплатен наставнички профил!"
     play_as: "Играј како" # Ladder page
-#    get_course_for_class: "Assign Game Development and more to your classes!"
-#    request_licenses: "Contact our school specialists for details."
-#    compete: "Compete!" # Course details page
+    get_course_for_class: "Додели развивање игри и повеќе во твојата училница!"
+    request_licenses: "Контактирајте ги нашите школски специјалисти за помош."
+    compete: "Натпреварувај се!" # Course details page
     spectate: "Набљудувај" # Ladder page
-    players: "играчи" # Hover over a level on /play
-    hours_played: "изиграни часови" # Hover over a level on /play
+    players: "Играчи" # Hover over a level on /play
+    hours_played: "Изиграни часови" # Hover over a level on /play
     items: "Опрема" # Tooltip on item shop button from /play
     unlock: "Отклучи" # For purchasing items and heroes
     confirm: "Потврди"
@@ -242,29 +242,29 @@
     heroes: "Херои" # Tooltip on hero shop button from /play
     achievements: "Постигнувања" # Tooltip on achievement list button from /play
     settings: "Подесувања" # Tooltip on settings button from /play
-#    poll: "Poll" # Tooltip on poll button from /play
+    poll: "Гласање" # Tooltip on poll button from /play
     next: "Следно" # Go from choose hero to choose inventory before playing a level
     change_hero: "Смени херој" # Go back from choose inventory to choose hero
-#    change_hero_or_language: "Change Hero or Language"
+    change_hero_or_language: "Смени херој или јазик"
     buy_gems: "Купи скапоцени камења"
-#    subscribers_only: "Subscribers Only!"
-#    subscribe_unlock: "Subscribe to Unlock!"
-#    subscriber_heroes: "Subscribe today to immediately unlock Amara, Hushbaum, and Hattori!"
-#    subscriber_gems: "Subscribe today to purchase this hero with gems!"
+    subscribers_only: "Само за претплатници!"
+    subscribe_unlock: "Претплати се за да отклучиш!"
+    subscriber_heroes: "Претплати се денес за веднаш да ги отклучиш Амара, Хушбаум и Хатори!"
+    subscriber_gems: "Претплати се денес за да го купиш овој херој со скапоцени камења!"
     anonymous: "Анонимен играч"
-    level_difficulty: "Тешкотија: "
+    level_difficulty: "Ниво на тешкотија: "
     awaiting_levels_adventurer_prefix: "Пуштаме пет нивоа неделно." # {change}
     awaiting_levels_adventurer: "Зачлени се како Авантурист"
-    awaiting_levels_adventurer_suffix: "за да бидеш првиот кој ќе ги игра новите нивоа."
-#    adjust_volume: "Adjust volume"
+    awaiting_levels_adventurer_suffix: "За да бидеш првиот кој ќе ги игра новите нивоа."
+    adjust_volume: "Подеси звук"
     campaign_multiplayer: "Арени за повеќе играчи"
     campaign_multiplayer_description: "... во кои кодираш лице-во-лице против други играчи."
-#    brain_pop_done: "You’ve defeated the Ogres with code! You win!"
-#    brain_pop_challenge: "Challenge yourself to play again using a different programming language!"
-#    replay: "Replay"
-#    back_to_classroom: "Back to Classroom"
-#    teacher_button: "For Teachers"
-#    get_more_codecombat: "Get More CodeCombat"
+    brain_pop_done: "Ги порази огрите со код! Победи!"
+    brain_pop_challenge: "Предизвикај се себеси да играш пак со различен програмски јазик!"
+    replay: "Играј повторно"
+    back_to_classroom: "Назад во училницата"
+    teacher_button: "За наставници"
+    get_more_codecombat: "Повеќе CodeCombat"

 #  code:
 #    if: "if" # Keywords--these translations show up on hover, so please translate them all, even if it's kind of long. (In the code editor, they will still be in English.)


Update pt-BR.coffee [subscribe]

Update pt-BR.coffee [subscribe]

I only updated the 'subscribe' section here.
I improved some translations but the main change was on the mixing translations for the word 'subscribe' and its contractions:
I believe the best translation for "subscription" would be "assinatura" instead of "inscrição". The former is used in magazine and cable TV subscriptions context, for example. I found that both words were being used and I decided to change everything to "assinatura".

--- app/locale/pt-BR.coffee
+++ app/locale/pt-BR.coffee
@@ -715,31 +715,31 @@
     prompt_body: "Continue jogando para ganhar mais!"

   subscribe:
-    best_deal: "Melhore negócio!"
+    best_deal: "Melhor negócio!"
     confirmation: "Parabéns! Agora você têm uma conta Premium!"
-    premium_already_subscribed: "Você já se inscreveu como Premium!"
+    premium_already_subscribed: "Você já é um assinante Premium!"
     subscribe_modal_title: "CodeCombat Premium"
-    comparison_blurb: "Seja um programador. Faça a inscrição Premium hoje!" # {change}
-    must_be_logged: "Você deve estar logado primeiro. Por favor crie uma conta ou faça log in no menu acima."
-    subscribe_title: "Inscrever-se" # Actually used in subscribe buttons, too
-    unsubscribe: "Desinscrever-se"
-    confirm_unsubscribe: "Confirmar Desinscrição"
-    never_mind: "Nunca esqueça, eu ainda te você"
-    thank_you_months_prefix: "Obrigado por nos apoiar nos últimos"
+    comparison_blurb: "Se torne um mestre programador - assine como Premium hoje!" # {change}
+    must_be_logged: "Você precisa estar logado antes. Por favor, crie uma conta ou faça login no menu acima."
+    subscribe_title: "Assinar" # Actually used in subscribe buttons, too
+    unsubscribe: "Cancelar Assinatura"
+    confirm_unsubscribe: "Confirmar o cancelamento"
+    never_mind: "Deixa pra lá, eu ainda te amo"
+    thank_you_months_prefix: "Obrigado por nos apoiar nesses últimos"
     thank_you_months_suffix: "meses."
     thank_you: "Obrigado por apoiar o CodeCombat."
-    sorry_to_see_you_go: "É uma pena ver você partir! Por favor, diga o que podemos fazer para melhorar."
+    sorry_to_see_you_go: "É uma pena ver você partir! Por favor, conte-nos o que poderíamos ter feito melhor."
     unsubscribe_feedback_placeholder: "Oh, o que nós fizemos?"
-    stripe_description: "Inscrição Mensal"
+    stripe_description: "Assinatura Mensal"
     buy_now: "Compre Agora"
-    subscription_required_to_play: "Você precisará se inscrever para jogar este nível."
-    unlock_help_videos: "Inscreva-se para desbloquear todos os vídeos tutoriais."
-    personal_sub: "Inscrição individual" # Accounts Subscription View below
-    loading_info: "Carregando informação sobre assinatura..."
+    subscription_required_to_play: "Você precisa de uma assinatura para jogar este nível."
+    unlock_help_videos: "Assine para desbloquear todos os vídeos de tutorial."
+    personal_sub: "Assinatura Individual" # Accounts Subscription View below
+    loading_info: "Carregando informações sobre assinatura..."
     managed_by: "Gerenciada por"
     will_be_cancelled: "Será cancelada em"
-    currently_free: "Atualmente você tem uma assinatura gratuita"
-    currently_free_until: "Sua assinatura gratuita é válida até"
+    currently_free: "Você tem uma assinatura gratuita no momento"
+    currently_free_until: "Sua assinatura é válida até"
     free_subscription: "Assinatura gratuita"
     was_free_until: "Sua assinatura gratuita expirou em"
     managed_subs: "Assinaturas gerenciadas"
@@ -749,15 +749,15 @@
     subscribe_prepaid: "Clique em Assinar para utilizar um código pré-pago"
     using_prepaid: "Utilizando código pré-pago para assinatura mensal"
     feature_level_access: "Acesso a mais de 300+ níveis"
-    feature_heroes: "Desbloqueie heróis exclusivos" # {change}
+    feature_heroes: "Desbloqueie heróis e mascotes exclusivos"
     feature_learn: "Aprenda a fazer jogos e websites"
     month_price: "$__price__/mês" # {change}
     first_month_price: "Apenas $__price__ no primeiro mês!"
-    lifetime: "Inscrição para vida toda" # {change}
+    lifetime: "Assinatura Vitalícia" # {change}
     lifetime_price: "$__price__"
-    year_subscription: "Inscrição Anual"
+    year_subscription: "Assinatura Anual"
     year_price: "$__price__/ano"
-    support_part1: "Precisa de ajuda com as opções de pagamento? Envie um email para" # {change}
+    support_part1: "Precisa de ajuda com o pagamento ou prefere PayPal? Envie um email para" # {change}
     support_part2: "support@codecombat.com"

   announcement:


修正Entity对$开头Entity属性的错误处理

修正Entity对$开头Entity属性的错误处理

--- src/core/entity.coffee
+++ src/core/entity.coffee
@@ -527,12 +527,13 @@

 				if dataType
 					if value?
-						if dataType instanceof cola.StringDataType and typeof value != "string" or dataType instanceof cola.BooleanDataType and typeof value != "boolean" or dataType instanceof cola.NumberDataType and typeof value != "number" or dataType instanceof cola.DateDataType and !(value instanceof Date)
+						if dataType instanceof cola.StringDataType and typeof value isnt "string" or dataType instanceof cola.BooleanDataType and
+						  typeof value isnt "boolean" or dataType instanceof cola.NumberDataType and typeof value isnt "number" or dataType instanceof cola.DateDataType and not (value instanceof Date)
 							value = dataType.parse(value)
 						else if dataType instanceof cola.EntityDataType
 							matched = true
 							if value instanceof _Entity
-								matched = value.dataType == dataType and !property._aggregated
+								matched = value.dataType == dataType and not property._aggregated
 							else if value instanceof _EntityList
 								matched = value.dataType == dataType and property._aggregated
 							else if property._aggregated or value instanceof Array or value.hasOwnProperty("$data") or value.hasOwnProperty("data$")


Enable all toolbar dialog input elements when showing. Fixes #159

--- src/trix/controllers/toolbar_controller.coffee
+++ src/trix/controllers/toolbar_controller.coffee
@@ -117,9 +117,11 @@
     element = @getDialog(dialogName)
     element.classList.add("active")

+    for disabledInput in element.querySelectorAll("input[disabled]")
+      disabledInput.removeAttribute("disabled")
+
     if attributeName = getAttributeName(element)
       if input = getInputForDialog(element, dialogName)
-        input.removeAttribute("disabled")
         input.value = @attributes[attributeName] ? ""
         input.select()


reset demo state when user leaves homepage

--- app/routes/index.coffee
+++ app/routes/index.coffee
@@ -1,4 +1,9 @@
 route = Ember.Route.extend
+  # Reset the demo state
+  deactivate: () ->
+    controller = @get 'controller'
+    controller.setProperties { isDemoActive: false, isSignupActive: false }
+
   actions:
     showDemo: ->
       @get('controller').set('isDemoActive', true)


Auto make log global when you include it

--- log.coffee
+++ log.coffee
@@ -65,6 +65,6 @@
 			results.push 'ELSE_TYPE: '+arg+' ['+typeof arg+']'
 	return results.join(', ')

-log = module.exports = ->
+global.log = module.exports = ->
 	# Output Object Introspection
 	console.log ' log: '+format.apply this, arguments


Ignore importe layer name suffixes

Ignore importe layer name suffixes

Like .png or -, *

--- framer/Importer.coffee
+++ framer/Importer.coffee
@@ -29,6 +29,12 @@

 startsWithNumber = (str) ->
 	return (new RegExp("^[0-9]")).test(str)
+
+sanitizeLayerName = (name) ->
+	for suffix in ["*", "-", ".png", ".jpg", ".pdf"]
+		if _.endsWith(name.toLowerCase(), suffix)
+			name = name[0..name.length-suffix.length-1]
+	return name

 class exports.Importer

@@ -102,7 +108,7 @@

 		layerInfo =
 			shadow: true
-			name: info.name
+			name: sanitizeLayerName(info.name)
 			frame: info.layerFrame
 			clip: false
 			backgroundColor: null


add Colormodel to Animation

add Colormodel to Animation

--- framer/Animation.coffee
+++ framer/Animation.coffee
@@ -54,6 +54,7 @@
 			repeat: 0
 			delay: 0
 			debug: false
+			colorModel: "husl"

 		if options.origin
 			console.warn "Animation.origin: please use layer.originX and layer.originY"
@@ -192,7 +193,7 @@
 		for k, v of @_stateB

 			if Color.isColor(v) or Color.isColor(@_stateA[k])
-				@_target[k] = Color.mix(@_stateA[k], @_stateB[k], value)
+				@_target[k] = Color.mix(@_stateA[k], @_stateB[k], value, false, @options.colorModel)
 			else
 				@_target[k] = Utils.mapRange(value, 0, 1, @_stateA[k], @_stateB[k])


Introducing environments. Not yet ready.

--- src/lib/docpad.coffee
+++ src/lib/docpad.coffee
@@ -501,8 +501,7 @@

 		# Max Age
 		# The caching time limit that is sent to the client
-		# Defaults to false if not on production, otherwise one day
-		maxAge: null
+		maxAge: false

 		# -----------------------------
@@ -526,10 +525,6 @@

 		# -----------------------------
 		# Other
-
-		# Environment
-		# Whether or not we are in production or development
-		env: process.env.NODE_ENV or 'development'

 		# Node Path
 		# The location of our node executable
@@ -554,8 +549,7 @@

 		# Check Version
 		# Whether or not to check for newer versions of DocPad
-		# Defaults to true if not on production, otherwise false
-		checkVersion: null
+		checkVersion: true

 		# Collections
 		# A hash of functions that create collections
@@ -564,6 +558,21 @@
 		# Events
 		# A hash of event handlers
 		events: null  # {}
+
+
+		# -----------------------------
+		# Environment Configuration
+
+		# Environment
+		# Whether or not we are in production or development
+		env: process.env.NODE_ENV or 'development'
+
+		# Environments
+		# Environment specific configuration to over-ride the global configuration
+		environments:
+			production:
+				checkVersion: false
+				maxAge: 86400000

 	# Get Environment


Remove outdated mark

Remove outdated mark

--- data/prefixes.coffee
+++ data/prefixes.coffee
@@ -48,16 +48,14 @@
 feature require('caniuse-db/features-json/border-radius'), (browsers) ->
   prefix 'border-radius', 'border-top-left-radius', 'border-top-right-radius',
          'border-bottom-right-radius', 'border-bottom-left-radius',
-          mistakes:   ['-khtml-', '-ms-', '-o-']
-          transition: true
-          browsers:   browsers
+          mistakes: ['-khtml-', '-ms-', '-o-']
+          browsers: browsers

 # Box Shadow
 feature require('caniuse-db/features-json/css-boxshadow'), (browsers) ->
   prefix 'box-shadow',
-          mistakes:   ['-khtml-']
-          transition: true
-          browsers:   browsers
+          mistakes: ['-khtml-']
+          browsers: browsers

 # Animation
 feature require('caniuse-db/features-json/css-animation'), (browsers) ->
@@ -78,14 +76,12 @@
 # Transform 2D
 feature require('caniuse-db/features-json/transforms2d'), (browsers) ->
   prefix 'transform', 'transform-origin',
-          transition: true
-          browsers:   browsers
+          browsers: browsers

 # Transform 3D
 feature require('caniuse-db/features-json/transforms3d'), (browsers) ->
   prefix 'perspective', 'perspective-origin',
-          transition: true
-          browsers:   browsers
+          browsers: browsers

   prefix 'transform-style', 'backface-visibility',
           mistakes: ['-ms-', '-o-']
@@ -116,8 +112,7 @@
 # Filter Effects
 feature require('caniuse-db/features-json/css-filters'), (browsers) ->
   prefix 'filter',
-          transition: true
-          browsers:   browsers
+          browsers: browsers

 # filter() function
 feature require('caniuse-db/features-json/css-filter-function'), (browsers) ->
@@ -129,8 +124,7 @@
 # Backdrop-filter
 feature require('caniuse-db/features-json/css-backdrop-filter'), (browsers) ->
   prefix 'backdrop-filter',
-          transition: true
-          browsers:   browsers
+          browsers: browsers

 # element() function
 feature require('caniuse-db/features-json/css-element-function'), (browsers) ->
@@ -143,12 +137,11 @@
 feature require('caniuse-db/features-json/multicolumn'), (browsers) ->
   prefix 'columns', 'column-width', 'column-gap',
          'column-rule', 'column-rule-color', 'column-rule-width',
-          transition: true
-          browsers:   browsers
+          browsers: browsers

   prefix 'column-count', 'column-rule-style', 'column-span', 'column-fill',
          'break-before', 'break-after', 'break-inside',
-          browsers:   browsers
+          browsers: browsers

 # User select
 feature require('caniuse-db/features-json/user-select-none'), (browsers) ->
@@ -165,7 +158,6 @@
           props:  ['display']
           browsers: browsers
   prefix 'flex', 'flex-grow', 'flex-shrink', 'flex-basis',
-          transition: true
           browsers:   browsers
   prefix 'flex-direction', 'flex-wrap', 'flex-flow', 'justify-content',
          'order', 'align-items', 'align-self', 'align-content',
@@ -290,8 +282,7 @@
   prefix 'clip-path', 'mask', 'mask-position', 'mask-size',
          'mask-border', 'mask-border-outset', 'mask-border-width',
          'mask-border-slice',
-          transition: true
-          browsers:   browsers
+          browsers: browsers

 # Fragmented Borders and Backgrounds
 feature require('caniuse-db/features-json/css-boxdecorationbreak'), (brwsrs) ->
@@ -357,15 +348,13 @@
   prefix 'border-inline-start',  'border-inline-end',
          'margin-inline-start',  'margin-inline-end',
          'padding-inline-start', 'padding-inline-end',
-          transition: true
-          browsers:   browsers
+          browsers: browsers

 feature logicalProps, match: /x\s#2/, (browsers) ->
   prefix 'border-block-start',  'border-block-end',
          'margin-block-start',  'margin-block-end',
          'padding-block-start', 'padding-block-end',
-          transition: true
-          browsers:   browsers
+          browsers: browsers

 # CSS appearance
 feature require('caniuse-db/features-json/css-appearance'), (browsers) ->


fixing profile pictures

fixing profile pictures

Former-commit-id: aa916136e6f5042fd72759b109e1198803a8914d [formerly 17150658f7b0623c78f16434eac92ce01114ddbc] [formerly 846a9f82914e69a31e145859a1bf70a21853e8b3 [formerly 98d80a8c798780688f9d0399217324e43773cba9]] [formerly c67404dd972735376872cdc1f2f004211b05e8d9 [formerly ef2f6822c408b746066fbb6d35ca00fe0420f8cc] [formerly 00f1c669c017df5d7424bc0cea9d7b30343c51f8 [formerly 00f1c669c017df5d7424bc0cea9d7b30343c51f8 [formerly bdd3b0c306100d8e743a3a5c56c63ba4b6ea08ef]]]]
Former-commit-id: 1541769949dba959f8dce833f10d162bd44deb9a [formerly f5f47eba71c517fb12effe9a877c1fb0ae410520] [formerly 00f123e0c7a51a2dd46c9bbaaaa4dea632cfd6ee [formerly 80244b7023eb66b0618d6040251ecb28916fe140]]
Former-commit-id: 3fd141028cee7a8aaf5b8cea1a37a9e580d223de [formerly 63bf0c78779d925edc50ff37eb24908579dcd62f]
Former-commit-id: 906350bc0a610f9852e3f9f5b350ffa221078080
Former-commit-id: e5bc5db9adf682b3d8800335c27d6542fbed893f

--- _old@client/modules/dash/profile_controller.coffee
+++ _old@client/modules/dash/profile_controller.coffee
@@ -26,7 +26,15 @@
     setupLayout : ->
       layout = @getLayout()
       @listenTo layout, 'user:update:requested', (data) =>
-        App.request "user:update_current_user", data
+        if data.key?
+          updated_user = data
+        else
+          updated_user = {}
+          for updated in data
+            if updated.key == '/current_user'
+              updated_user = updated
+
+        App.request "user:update_current_user", updated_user
         layout.render()
       layout


Try to improve the unread mark a little

Try to improve the unread mark a little

--- packages/rocketchat-ui/lib/readMessages.coffee
+++ packages/rocketchat-ui/lib/readMessages.coffee
@@ -22,6 +22,9 @@
 	readNow: (force=false) ->
 		console.log '--------------' if @debug
 		console.log 'readMessage -> readNow init process force:', force if @debug
+
+		self.refreshUnreadMark()
+
 		self = @
 		if force isnt true and @canReadMessage is false
 			console.log 'readMessage -> readNow canceled by canReadMessage: false' if @debug
@@ -54,11 +57,13 @@
 			return

 		# Only read messages if user saw the first unread message
-		position = $('.message.first-unread').position()
-		visible = position?.top >= 0
-		if not visible and room.unreadSince.get()?
-			console.log 'readMessage -> readNow canceled, unread mark visible:', visible, 'unread since exists', room.unreadSince.get()? if @debug
-			return
+		unreadMark = $('.message.first-unread')
+		if unreadMark.length > 0
+			position = unreadMark.position()
+			visible = position?.top >= 0
+			if not visible and room.unreadSince.get()?
+				console.log 'readMessage -> readNow canceled, unread mark visible:', visible, 'unread since exists', room.unreadSince.get()? if @debug
+				return

 		console.log 'readMessage -> readNow rid:', rid if @debug
 		Meteor.call 'readMessages', rid, ->


调试完成列表模板

调试完成列表模板

--- src/static_gen.coffee
+++ src/static_gen.coffee
@@ -56,7 +56,7 @@
     tmplFunc = tmplMap[config.TEMPLATE]
     if "function" != typeof tmplFunc
         throw new Error("Template not found : #{config.TEMPLATE}")
-    # filter metas
+    # filter metasArray
     metasArray = metasArray.filter (meta) ->
         if not meta.type
             return true
@@ -64,15 +64,15 @@
     # compile template
     item = config.CATALOG.item or DEFAULT_ITEM
     multi = config.CATALOG.multi or DEFAULT_MULTI
-    config.ITEM = {}
+    config.ITEMS = {}
     if not multi
-        config.ITEM.pageIndex = 1
-        config.ITEM.metasArray = metasArray[0...item]
+        config.ITEMS.pageIndex = 1
+        config.ITEMS.metasArray = metasArray[0...item]
         return tmplFunc(config)
     else
         for idx in [0...metasArray.length] by item
-            config.ITEM.pageIndex = idx + 1
-            config.ITEM.metasArray = metasArray[idx...item]
+            config.ITEMS.pageIndex = idx + 1
+            config.ITEMS.metasArray = metasArray[idx...item]
             htmlText = tmplFunc(config)
             tmplsArray.push(htmlText)
         return tmplsArray
@@ -197,30 +197,31 @@
             color.yellow(error.stack)

     # build .sg file
-    sgConfigArray = []
+    sgMetasArray = []
     for sgInfo in sgInfosArray
         try
             sgConfig = readTmplConfig(virFs, sgInfo)
-            sgConfigArray.push(sgConfig)
+            sgMetasArray.push(sgConfig.META)
             tmplBuffer = buildTmplSg(tmplsMap, sgConfig)
-            rlsFs.write(sgInfo.name, tmplBuffer)
+            outName = path.normalize("#{sgInfo.url.dir}/#{sgConfig.NAME}.html")
+            rlsFs.write(outName, tmplBuffer)
         catch error
             color.yellow("Build ERR : #{virInfo.name}")
             color.yellow(error.stack)

-    return
     # build .xsg file
     for xsgInfo in xsgInfosArray
         try
-            xsgConfig = readTmplConfig(virFs, sgInfo)
-            result = buildTmplXsg(tmplsMap, xsgConfig)
+            xsgConfig = readTmplConfig(virFs, xsgInfo)
+            result = buildTmplXsg(tmplsMap, sgMetasArray, xsgConfig)
             if "string" == typeof result
                 tmplBuffer = result
-                rls.write(virInfo.name, tmplBuffer)
+                outName = path.normalize("#{xsgInfo.url.dir}/#{xsgConfig.NAME}.html")
+                rlsFs.write(outName, tmplBuffer)
             else
                 for tmplBuffer, idx in result
-                    name = "#{virInfo.name}_#{idx+1}"
-                    rls.write(name, tmplBuffer)
+                    outName = path.normalize("#{xsgInfo.url.dir}/#{xsgConfig.NAME}_#{idx+1}.html")
+                    rlsFs.write(name, tmplBuffer)
         catch error
             color.yellow("Build ERR : #{virInfo.name}")
             color.yellow(error.stack)


Removed duplicated code

Removed duplicated code

--- src/step/phone.coffee
+++ src/step/phone.coffee
@@ -94,16 +94,6 @@
       el.find('.method-summary').text(i18n.trans("code.service.#{widgetType}.summary"))
       el.find('.method-input').remove()
       el.find('.js-send-code').text(i18n.trans("code.service.#{widgetType}.label"));
-
-    if 'V' not in api.data.features and widgetType is 'voice'
-      widgetType = 'sms'
-    else if 'D' not in api.data.features and widgetType is 'dual'
-      widgetType = 'sms'
-
-    if widgetType in ['sms', 'voice']
-      el.find('.method-summary').text(i18n.trans("widget.code.service.#{widgetType}.summary"))
-      el.find('.method-input').remove()
-      el.find('.js-send-code').text(i18n.trans("widget.code.service.#{widgetType}.label"));

     co = {};


subprocess: Use debug()

subprocess: Use debug()

--- src/subprocess.coffee
+++ src/subprocess.coffee
@@ -1,7 +1,9 @@

 # For starting the runtime used in test
 child_process = require 'child_process'
+common = require './common'

+debug = common.debug

 exports.start = (command, callback) ->

@@ -13,9 +15,9 @@
     return callback err

   child.stdout.on 'data', (data) ->
-    console.log data.toString()
+    debug data.toString()
   child.stderr.on 'data', (data) ->
-    console.log data.toString()
+    debug data.toString()

   # give process some time to open port
   setTimeout callback, 2000


Remove the explorer links, it's not quite ready for showtime.

This reverts commit cae58e29ab32a4c6d6211768c51e0f4326270eb3.

--- gae/scripts/view.coffee
+++ gae/scripts/view.coffee
@@ -770,7 +770,7 @@

         exploreLink = ExploreLink.fromPartialHistory @calls
         exploreLink.textContent = "open in bid explorer"
-        @appendChild exploreLink
+        #@appendChild exploreLink

     @fromBoardAndCalls: (board, calls) ->
         return alloc @, board, calls


fix coffeelint warning

fix coffeelint warning

--- src/scripts/controllers/mapController.coffee
+++ src/scripts/controllers/mapController.coffee
@@ -196,7 +196,7 @@
     )

   # Change our popup text as needed
-  updateMarkerPopup: (marker, payload) =>
+  updateMarkerPopup: (marker, payload) ->
     if marker?
       # We merge the misc payload fields into one dictionary - showing the latest combination of all data
       angular.extend(marker.payload, payload)


handles error on the front page

handles error on the front page

--- apps/root/routes.coffee
+++ apps/root/routes.coffee
@@ -36,12 +36,13 @@
         per: 10
       cache: true
       success: render
+      error: next

     stats = new Backbone.Model
     stats.url = "#{sd.API_URL}/utilities/statistics"
     stats.formatNumber = (attr) -> @get(attr).toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,")

-    stats.fetch cache: true, success: render
+    stats.fetch cache: true, success: render, error: next

 @notifications = (req, res, next) ->
   res.locals.sd.FEED_TYPE = 'notifications'


change query params

change query params

--- src/smc-hub/postgres-user-query-queue.coffee
+++ src/smc-hub/postgres-user-query-queue.coffee
@@ -15,7 +15,7 @@
 # We do at most this many user queries **at once** to the database on behalf
 # of each connected client.  This only applies when the global limit has
 # been exceeded.
-USER_QUERY_LIMIT = 5
+USER_QUERY_LIMIT = 1

 # If we don't even start query by this long after we receive query, then we consider it failed
 USER_QUERY_TIMEOUT_MS = 15000
@@ -28,7 +28,7 @@
 # many global outstanding concurrent **user queries**.  The point is that
 # if there's very little load, we should get queries done as fast
 # as possible for users.
-GLOBAL_LIMIT = 200
+GLOBAL_LIMIT = 150

 # setup metrics
 metrics_recorder = require('./metrics-recorder')


Revert "Fix Application constructor."
Wrong branch.

This reverts commit 222b51e8caaa0f5ae4ddb430624ea458de24b1b2.

--- system/Application.coffee
+++ system/Application.coffee
@@ -22,6 +22,7 @@
       @killHandlers = []
       @console = null # The main text I/O console for this application.
       throw "#{main} is not an application main module" if not @mod.initApp?
+      @obj.initApp(this) # Initialize the application.

     init: (id) ->
       @obj.initApp(this, id)