history viewer: set editor using a more sensible approach.

--- salvus/page/editor.coffee
+++ salvus/page/editor.coffee
@@ -2991,17 +2991,22 @@
         return obj

     goto_revision: (num) ->
+        #t = misc.mswalltime()
         @element.find(".salvus-editor-history-revision-number").text("Revision " + num)
         if num == 0
             @element.find(".salvus-editor-history-revision-time").text("")
         else
             @element.find(".salvus-editor-history-revision-time").text(new Date(@parse_logstring(@log[@nlines-num+1]).time).toLocaleString())
         if @revision_num - num > 2
+            #console.log("goto_revision #{num} from #{@revision_num}"); t = misc.mswalltime()
             text = @history_editor.codemirror.getValue()
             text_old = text
+            #console.log("got value", misc.mswalltime(t)); t = misc.mswalltime()
             for patch in @log[(@nlines-@revision_num+1)..(@nlines-num)]
                 text = diffsync.dmp.patch_apply(@parse_logstring(patch).patch, text)[0]
-            @diffsync.patch_in_place(diffsync.dmp.patch_make(text_old, text))
+            #console.log("patched text", misc.mswalltime(t)); t = misc.mswalltime()
+            @history_editor.codemirror.setValue(text)
+            #console.log("changed editor", misc.mswalltime(t)); t = misc.mswalltime()
         else if @revision_num > num
             for patch in @log[(@nlines-@revision_num+1)..(@nlines-num)]
                 @diffsync.patch_in_place(@parse_logstring(patch).patch)
@@ -3010,7 +3015,7 @@
             text_old = text
             for patch in @log[(@nlines-num+1)..(@nlines-@revision_num)].reverse()
                 text = diffsync.dmp.patch_apply(@parse_logstring_inverse(patch).patch, text)[0]
-            @diffsync.patch_in_place(diffsync.dmp.patch_make(text_old, text))
+            @history_editor.codemirror.setValue(text)
         else
             for patch in @log[(@nlines-num+1)..(@nlines-@revision_num)].reverse()
                 @diffsync.patch_in_place(@parse_logstring_inverse(patch).patch)
@@ -3025,6 +3030,7 @@
             @forward_button.removeClass("disabled")
         if @ext == 'sagews'
             @worksheet.process_sage_updates()
+        #console.log("going to revision #{num} took #{misc.mswalltime(t)}ms")

     render_history: (first) =>
         @log = @file_history.live().split("\n")


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/number_extensions.coffee
+++ test/number_extensions.coffee
@@ -16,7 +16,8 @@
   equals (-1.2).ceil(), -1, "(-1.2).ceil() equals -1"
   equals 3.ceil(), 3, "(3).ceil() equals 3"

-test "#clamp"
+test "#clamp", ->
+  equals

 test "#floor", ->
   equals 4.9.floor(), 4, "(4.9).floor() equals 4"


correctly escape the -

correctly escape the -

--- src/scripts/jenkins.coffee
+++ src/scripts/jenkins.coffee
@@ -11,7 +11,7 @@
 # jenkins list - lists Jenkins jobs
 #
 module.exports = (robot) ->
-  robot.respond /jenkins build ([\w\.-_]+)( with (.+))?/i, (msg) ->
+  robot.respond /jenkins build ([\w\.\-_]+)( with (.+))?/i, (msg) ->

     url = process.env.HUBOT_JENKINS_URL
     job = msg.match[1]


Debugging

Debugging

--- src/highfive.coffee
+++ src/highfive.coffee
@@ -15,6 +15,10 @@
 Path = require 'path'
 fs = require 'fs'
 coffee = require 'coffee-script'
+
+debug = ->
+if process.env.HUBOT_HIGHFIVE_DEBUG?
+    debug = (msg, txt) -> msg.send "DEBUG " + txt

 module.exports = (robot) ->
     # Services for getting emails from users
@@ -80,8 +84,10 @@
         to_user = msg.match[1][1..]
         amt = parseInt(msg.match[3] or 0)
         reason = msg.match[4]
+        debug msg, "from `#{from_user}` to `#{to_user}` amount `#{amt}` reason `#{reason}`"

         email_fetcher from_user, to_user, (from_email, to_email) ->
+            debug msg, "from #{from_email} to #{to_email}"
             # Safety checks:
             # - Don't target a nonexistent user
             # - Don't target yourself
@@ -107,6 +113,8 @@
                 acct = process.env.HUBOT_TANGOCARD_ACCOUNT

                 tango.getAccountStatus cust, acct, (resp) ->
+                    debug msg, "account status `#{JSON.stringify resp}`"
+
                     unless resp.success
                         return msg.send "(Problem getting Tango Card status: '#{resp.error_message}'. You might want 'highfive config'.)"
                     return sendCard() if resp.account.available_balance/100 >= amt
@@ -119,6 +127,7 @@
                         jsonip = JSON.parse body

                         tango.fundAccount cust, acct, amtToFund, jsonip.ip, cc, auth, (resp) ->
+                            debug msg, "funding response `#{JSON.stringify resp}`"
                             unless resp.success
                                 return msg.send "(Problem funding Tango Card account: '#{resp.denial_message}'. You might want 'highfive config'.)"
                             return sendCard() if resp.success
@@ -126,10 +135,10 @@
                 sendCard = ->
                     message = "#{from_user} has high-fived you for #{reason}!"
                     tango.orderAmazonDotComCard cust, acct, 'High-five', amt*100, from_user, 'High Five!', to_user, to_email, message, (resp) ->
+                        debug msg, "order response `#{resp}`"
                         unless resp.success
                             errmsg = resp.invalid_inputs_message || resp.error_message || resp.denial_message
                             return msg.send "(Problem ordering gift card: '#{errmsg}'. You might want 'highfive config'.)"
-                        console.log resp
                         msg.send "A $#{25} gift card is on its way!"
                         # TODO: log to spreadsheet

@@ -208,7 +217,6 @@
             reward_subject: subject
             reward_message: message
             send_reward: true
-        console.log data
         @post 'orders', data, callback


Add check for end of input on parseError handler

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -178,7 +178,7 @@
 # Override Jison's default error handling function.
 parser.yy.parseError = (message, {token}) ->
   # Disregard Jison's message, it contains redundant line numer information.
-  message = "unexpected #{token}"
+  message = "unexpected #{if token is 1 then 'end of input' else token}"
   # The second argument has a `loc` property, which should have the location
   # data for this token. Unfortunately, Jison seems to send an outdated `loc`
   # (from the previous token), so we take the location information directly


Issue #1262, print filename in error messages for require module scripts.

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -14,7 +14,7 @@
 # TODO: Remove registerExtension when fully deprecated.
 if require.extensions
   require.extensions['.coffee'] = (module, filename) ->
-    content = compile fs.readFileSync filename, 'utf8'
+    content = compile fs.readFileSync(filename, 'utf8'), {filename}
     module._compile content, filename
 else if require.registerExtension
   require.registerExtension '.coffee', (content) -> compile content


response type set to json

response type set to json

--- coffee/routes.coffee
+++ coffee/routes.coffee
@@ -1,7 +1,7 @@
 todos = require('./todos')

 respondWithTodos = (res) ->
-  res.send(JSON.stringify(todos.getAllTodos()))
+  res.json(JSON.stringify(todos.getAllTodos()))

 routes =
   registerRoutes: (app) ->
@@ -10,7 +10,7 @@
     )

     app.get('/todos/:id', (req, res) ->
-      res.send(JSON.stringify(todos.getTodo(req.params.id)))
+      res.json(JSON.stringify(todos.getTodo(req.params.id)))
     )

     app.post('/todos/add', (req, res) ->


Fix missing callback on async.queue

Fix missing callback on async.queue

--- src/aggregate_service.coffee
+++ src/aggregate_service.coffee
@@ -10,8 +10,16 @@

   initialize: (@_eventStore, @_domainEventService) ->
     # proxy & queue public api
-    _queue = async.queue (payload) =>
-      payload.originalFunction.call @, payload.arguments..., payload.resolve, payload.reject
+    _queue = async.queue (payload, callback) =>
+      payload.originalFunction.call @, payload.arguments...
+      .then (aggregateId) ->
+        payload.resolve aggregateId
+        callback()
+
+      .catch (error) ->
+        payload.reject error
+        callback error
+
     , 1

     _proxy = (_originalFunctionName, _originalFunction) -> ->
@@ -29,63 +37,65 @@
         @[originalFunctionName] = _proxy originalFunctionName, originalFunction

-  create: (params, resolve, reject) ->
-    aggregateName  = params.name
-    aggregateProps = params.props
+  create: (params) ->
+    new Promise (resolve, reject) =>
+      aggregateName  = params.name
+      aggregateProps = params.props

-    aggregateDefinition = @getAggregateDefinition aggregateName
-    if not aggregateDefinition
-      err = new Error "Tried to create not registered AggregateDefinition '#{aggregateName}'"
-      return reject err
-
-    # create Aggregate
-    aggregate = new Aggregate aggregateName, aggregateDefinition
-    aggregate.create aggregateProps
-
-    .then =>
-      @_generateSaveAndTriggerDomainEvent aggregate, 'create', resolve, reject
-
-    .catch (err) =>
-      reject err
-
-
-  command: (params, resolve, reject) ->
-    aggregateId   = params.id
-    aggregateName = params.name
-    methodName    = params.methodName
-    methodParams  = params.methodParams
-
-    aggregateDefinition = @getAggregateDefinition aggregateName
-    if not aggregateDefinition
-      err = new Error "Tried to command not registered AggregateDefinition '#{aggregateName}'"
-      return reject err
-
-    repository = new Repository
-      aggregateName: aggregateName
-      aggregateDefinition: aggregateDefinition
-      eventStore: @_eventStore
-
-    # get the aggregate from the AggregateRepository
-    repository.findById aggregateId, (err, aggregate) =>
-      return reject err if err
-
-      if not aggregate
-        err = new Error "No #{aggregateName} Aggregate with given aggregateId #{aggregateId} found"
+      aggregateDefinition = @getAggregateDefinition aggregateName
+      if not aggregateDefinition
+        err = new Error "Tried to create not registered AggregateDefinition '#{aggregateName}'"
         return reject err

-      if !methodParams
-        methodParams = []
-
-      # EXECUTING
-      aggregate.command
-        name: methodName
-        params: methodParams
+      # create Aggregate
+      aggregate = new Aggregate aggregateName, aggregateDefinition
+      aggregate.create aggregateProps

       .then =>
-        @_generateSaveAndTriggerDomainEvent aggregate, methodName, resolve, reject
+        @_generateSaveAndTriggerDomainEvent aggregate, 'create', resolve, reject

       .catch (err) =>
         reject err
+
+
+  command: (params) ->
+    new Promise (resolve, reject) =>
+      aggregateId   = params.id
+      aggregateName = params.name
+      methodName    = params.methodName
+      methodParams  = params.methodParams
+
+      aggregateDefinition = @getAggregateDefinition aggregateName
+      if not aggregateDefinition
+        err = new Error "Tried to command not registered AggregateDefinition '#{aggregateName}'"
+        return reject err
+
+      repository = new Repository
+        aggregateName: aggregateName
+        aggregateDefinition: aggregateDefinition
+        eventStore: @_eventStore
+
+      # get the aggregate from the AggregateRepository
+      repository.findById aggregateId, (err, aggregate) =>
+        return reject err if err
+
+        if not aggregate
+          err = new Error "No #{aggregateName} Aggregate with given aggregateId #{aggregateId} found"
+          return reject err
+
+        if !methodParams
+          methodParams = []
+
+        # EXECUTING
+        aggregate.command
+          name: methodName
+          params: methodParams
+
+        .then =>
+          @_generateSaveAndTriggerDomainEvent aggregate, methodName, resolve, reject
+
+        .catch (err) =>
+          reject err

   _generateSaveAndTriggerDomainEvent: (aggregate, methodName, resolve, reject) ->


Added a dialog that pops up on "correctness"

--- src/app/rounds/rounds.coffee
+++ src/app/rounds/rounds.coffee
@@ -25,7 +25,7 @@
 rounds.factory "RoundsRes", [
   "$resource"
   ($resource) ->
-    return $resource("http://api.tumblr.com/v2/tagged?api_key=PLACEHOLDER&tag=:tag&callback=JSON_CALLBACK", {},
+    return $resource("http://api.tumblr.com/v2/tagged?api_key=iI6dl4tEgEt96yvRl1urojakH0Wk86544k2ooTuNxHxVGysBMm&tag=:tag&callback=JSON_CALLBACK", {},
       jsonp_query:
         tag: @tag
         method: "JSONP"
@@ -33,9 +33,9 @@
 ]

 class RoundsCtrl
-  @$inject = ['$scope', 'RoundsRes']
+  @$inject = ['$scope', 'RoundsRes', '$route']

-  constructor: ($scope, RoundsRes) ->
+  constructor: ($scope, RoundsRes, $route) ->
     $scope.type = 'series'
     all_tags = {
       'series': ["adventure time", "supernatural", "doctor who", "lotr", "harry potter"],
@@ -52,7 +52,10 @@

     $scope.$watch "guess", (guess) ->
       return 0  if not guess or guess.length is 0
-      $scope.correct = guess.match $scope.tag_regex
+      $scope.correct = $scope.tag_regex.test(guess)
+
+    $scope.$watch "correct", (correct) ->
+      $route.reload if correct

 rounds.controller 'RoundsCtrl', RoundsCtrl


Unify the update command to be install plugin@versiond

--- views/components/settings/parts/plugin-config.cjsx
+++ views/components/settings/parts/plugin-config.cjsx
@@ -189,12 +189,8 @@
       updating = @state.updating
       updating[index] = true
       npm.load npmConfig, (err) =>
-        if ifStableVersion @state.latest[plugins[index].packageName]
-          npm.commands.update [plugins[index].packageName], (er, data) ->
-            callback(index, er)
-        else
-          npm.commands.install ["#{plugins[index].packageName}@#{@state.latest[plugins[index].packageName]}"], (er,data) ->
-            callback(index, er)
+        npm.commands.install ["#{plugins[index].packageName}@#{@state.latest[plugins[index].packageName]}"], (er,data) ->
+          callback(index, er)
       @setState {updating}
   handleInstallAllComplete: (data, er) ->
     failStatus = @state.failStatus


clear call queue on connect -- this should prevent some edge cases that stupidly overload serve and slow user reconnect experience.

--- src/smc-util/client.coffee
+++ src/smc-util/client.coffee
@@ -231,6 +231,10 @@

         @on 'connected', @send_version

+        # Any outstanding calls made before connecting happened can't possibly succeed,
+        # so we clear all outstanding messages.
+        @on 'connected', @_clear_call_queue
+
         # IMPORTANT! Connection is an abstract base class.  Derived classes must
         # implement a method called _connect that takes a URL and a callback, and connects to
         # the Primus websocket server with that url, then creates the following event emitters:
@@ -626,12 +630,11 @@
             @send(opts.message)
             setTimeout(cb, 150)
             return
-
         id = opts.message.id ?= misc.uuid()

         @call_callbacks[id] =
             cb          : (args...) =>
-                if cb?
+                if cb? and @call_callbacks[id]?
                     cb()
                     cb = undefined
                 opts.cb(args...)
@@ -657,12 +660,11 @@
             # in case opts.timeout isn't set but opts.cb is, but user disconnects,
             # then cb would never get called, which throws off our call counter.
             # Note that the input to cb doesn't matter.
-            f = ->
-                if cb?
+            f = =>
+                if cb? and @call_callbacks[id]?
                     cb()
                     cb = undefined
             setTimeout(f, 120*1000)
-

     call: (opts={}) =>
         # This function:
@@ -706,6 +708,11 @@
             @_emit_mesg_info()
             #console.log('count (done):', @_call.count)
             @_update_calls()
+
+    _clear_call_queue: =>
+        for id, obj of @call_callbacks
+            obj.cb('disconnect')
+            delete @call_callbacks[id]

     call_local_hub: (opts) =>
         opts = defaults opts,


Added rule level config

Added rule level config

--- src/jadelint.coffee
+++ src/jadelint.coffee
@@ -8,7 +8,13 @@
 module.exports = jadelint = (filenames, reporter, callback) ->
     try
         conf = JSON.parse fs.readFileSync '.jadelintrc'
-        try rules.rules[rule]::level = level for rule, level of conf
+        for rule, confVal of conf
+            switch typeof value
+                when 'string'
+                    try rules.rules[rule]::level = confVal
+                when 'object'
+                    for key, val of confVal
+                        try rules.rules[rule]::[key] = val

     reporter ?= new Reporter()


added add-, remove-grammar

added add-, remove-grammar

--- lib/atom-ternjs.coffee
+++ lib/atom-ternjs.coffee
@@ -133,9 +133,20 @@
   registerEvents: ->
     @disposables.push atom.config.observe 'atom-ternjs.coffeeScript', =>
       if atom.config.get('atom-ternjs.coffeeScript')
+        @addGrammar('CoffeeScript')
         @provider.addSelector('.source.coffee')
       else
+        @removeGrammar('CoffeeScript')
         @provider.removeSelector('.source.coffee')
+
+  addGrammar: (grammar) ->
+    return unless @grammars.indexOf(grammar) is -1
+    @grammars.push grammar
+
+  removeGrammar: (grammar) ->
+    idx = @grammars.indexOf(grammar)
+    return if idx is -1
+    @grammars.splice(idx, 1)

   registerCommands: ->
     @disposables.push atom.commands.add 'atom-text-editor', 'tern:definition': (event) =>


Fix jsx props indent when property is like prop={somthing}

--- lib/auto-indent.coffee
+++ lib/auto-indent.coffee
@@ -331,7 +331,10 @@
             if isFirstTokenOfLine
               stackOfTokensStillOpen.push parentTokenIdx = stackOfTokensStillOpen.pop()
               if parentTokenIdx?
-                indentRecalc = @indentRow({row: row}, tokenStack[parentTokenIdx].firstCharIndentation, 1 )
+                if tokenStack[parentTokenIdx].type is JSXTAG_OPEN
+                  indentRecalc = @indentRow({row: row}, tokenStack[parentTokenIdx].firstCharIndentation, 0, 1)
+                else
+                  indentRecalc = @indentRow({row: row}, tokenStack[parentTokenIdx].firstCharIndentation, 1 )

             # re-parse line if indent did something to it
             if indentRecalc


groups: debit a "user" pack from group's subscription before invitation is redeemed

--- workers/social/lib/social/models/invitation.coffee
+++ workers/social/lib/social/models/invitation.coffee
@@ -20,8 +20,10 @@

   {ObjectRef, dash, daisy, secure, signature} = require 'bongo'

-  KodingError = require '../error'
-  JMail       = require './email'
+  KodingError  = require '../error'
+  JMail        = require './email'
+  JGroup       = require './group'
+  JPaymentPack = require './payment/pack'

   @share()

@@ -133,9 +135,17 @@
       isRedeemed = yes
     operation.$set = status: 'redeemed'  if isRedeemed

-    @update operation, (err)=>
-      return callback err  if err
-      @addRedeemer delegate, callback
+    JGroup.one slug: @group, (err, group) =>
+      callback err  if err
+      group.fetchSubscription (err, subscription) =>
+        callback err  if err
+        JPaymentPack.one tags: "user", (err, pack) =>
+          callback err  if err
+          subscription.debit pack, (err) =>
+            callback err  if err
+            @update operation, (err) =>
+              return callback err  if err
+              @addRedeemer delegate, callback

   # send invites from group dashboard
   @create = secure (client, group, email, options, callback)->


Pass the Discussion object to FactRelationView

--- core/app/backbone/views/fact_relations_view.coffee
+++ core/app/backbone/views/fact_relations_view.coffee
@@ -56,6 +56,7 @@
     if @model.relations()
       @factRelationSearchRegion.show new AddEvidenceView
         collection: @model.relations()
+        model: @model
       @factRelationsRegion.show new FactRelationsListView
         collection: @model.relations()
     else


refactoring tests

refactoring tests

--- test/EvernoteExportTest.coffee
+++ test/EvernoteExportTest.coffee
@@ -5,21 +5,21 @@
 util = require "util"
 crypto = require "crypto"

-describe "EvernoteExport(enml_filename)", () ->
+describe "EvernoteExport(enml_filename)", ->
   temp = require "temp"
   temp.track()
-  describe "#export(directory, cbDone)", () ->
+  describe "#export(directory, cbDone)", ->
     it "creates an export directory", (done) ->
       temp.mkdir "enml2md", (err, dirPath) ->
         enex = new EvernoteExport TestConfig.fixtures['1']
-        enex.export dirPath, () ->
+        enex.export dirPath, ->
           fs.stat dirPath, (err, stats) ->
             stats.isDirectory().should.be.true
             done err
     it "creates exported files", (done) ->
       temp.mkdir "enml2md", (err, dirPath) ->
         enex = new EvernoteExport TestConfig.fixtures['2']
-        enex.export dirPath, () ->
+        enex.export dirPath, ->
           fs.stat dirPath + "/Enml2md test fixture note 1.md", (err, stats) ->
             stats.isFile().should.be.true
             fs.stat dirPath + "/Enml2md test fixture note 2.md", (err, stats) ->
@@ -28,7 +28,7 @@
     it "creates an attachment direcotry", (done) ->
       temp.mkdir "enml2md", (err, dirPath) ->
         enex = new EvernoteExport TestConfig.fixtures['1']
-        enex.export dirPath, () ->
+        enex.export dirPath, ->
           fs.stat dirPath + "/resources", (err, stats) ->
             stats.isDirectory().should.be.true
             done err
@@ -39,13 +39,13 @@
         resourceDir = dirPath + "/resources"
         hash = "095619d89dbbd6a0c5704d57e444f708"
         filePath = resourceDir + "/" + hash + ".png"
-        enex.export dirPath, () ->
+        enex.export dirPath, ->
           assertFileMD5Hash(done, err, filePath, hash)
     it "can be called without a callback", (done) ->
       temp.mkdir "enml2md", (err, dirPath) ->
         enex = new EvernoteExport TestConfig.fixtures['1']
         enex.export dirPath
-        testDone = () ->
+        testDone = ->
           enex.count.should.equal 1
           if enex.done
             done()
@@ -53,12 +53,12 @@
             setTimeout testDone, 10
         setTimeout testDone, 10

-  describe "#each(cbEach, cbEnd)", () ->
-    it "sets total #count at cbEnd()", (done) ->
+  describe "#each(cbEach, cbEnd)", ->
+    it "sets total @count at cbEnd()", (done) ->
       enex = new EvernoteExport TestConfig.fixtures['2']
       cbEach = (note) -> # callback for each note
         # do nothing
-      cbEnd = () -> # callback for the end
+      cbEnd = -> # callback for the end
         enex.count.should.equal 2
         done()
       enex.each cbEach, cbEnd
@@ -74,19 +74,19 @@
         note.tags.should.contain "evernote"
         note.tags.length.should.equal 2
         note.content.should.equal "fixture content\n\n"
-      cbEnd = () -> # callback for the end
+      cbEnd = -> # callback for the end
         enex.count.should.equal 1
         done()
       enex.each cbEach, cbEnd
     it "can be called without cbEach()", (done) ->
       enex = new EvernoteExport TestConfig.fixtures['2']
-      cbEnd = () -> # callback for the end
+      cbEnd = -> # callback for the end
         enex.count.should.equal 2
         done()
       enex.each null, cbEnd
     it "can be called without both beEach() and cbEnd()", (done) ->
       enex = new EvernoteExport TestConfig.fixtures['2']
-      testDone = () ->
+      testDone = ->
         enex.count.should.equal 2
         done()
       setTimeout testDone, 20
@@ -101,7 +101,7 @@
   fd = fs.createReadStream filePath
   md5 = crypto.createHash "md5"
   md5.setEncoding "hex"
-  fd.on "end", () ->
+  fd.on "end", ->
     md5.end()
     md5.read().should.equal hash
     done err


proper saving when editing task type; redirect to task types after editing; clear form after creating new tasktype; display more information when neither oxalis or arbitrary was selected

--- app/assets/javascripts/admin/views/tasktype/task_type_form_view.coffee
+++ app/assets/javascripts/admin/views/tasktype/task_type_form_view.coffee
@@ -12,7 +12,7 @@
   template : _.template("""

Task types

-        <form action="/api/taskTypes" method="POST" class="form-horizontal">
+        <form method="POST" class="form-horizontal">

             Summary

@@ -53,7 +53,7 @@

             Allow Branchpoints

-              <input type="checkbox" id="branchPointsAllowed" name="branchPointsAllowed" checked>
+              <input type="checkbox" id="branchPointsAllowed" name="branchPointsAllowed" value="true" checked>

@@ -61,7 +61,7 @@

             Allow Soma clicking

-              <input type="checkbox" id="somaClickingAllowed" name="somaClickingAllowed" checked>
+              <input type="checkbox" id="somaClickingAllowed" name="somaClickingAllowed" value="true" checked>

@@ -162,32 +162,36 @@
   submitForm : (event) ->

     event.preventDefault()
+    target = $(event.target)

     if not @ui.form[0].checkValidity()
       Toast.error("Please supply all needed values.")
       return

-    target = $(event.target)
-
-    if not @options.isEditForm
-
-      url = target.attr("action")
-
-      $.ajax(
-        url : url
-        type: "post",
-        data: target.serialize(),
-      ).done((response) =>
-        Toast.message(response.messages)
+    url =
+      if @options.isEditForm
+        "/api/taskTypes/#{@model.get("id")}"
+      else
+        "/api/taskTypes"
+
+    $.ajax(
+      url : url
+      type: "post",
+      data: target.serialize(),
+    ).done((response) =>
+      Toast.message(response.messages)
+
+      if @options.isEditForm
+        app.router.navigate("/taskTypes", { trigger: true })
+      else
         @collection.addJSON(response.newTaskType)
-
-      ).fail((xhr) ->
-        Toast.message(xhr.responseJSON.messages)
-      )
-
-    else
-
-      # TODO ...
+        @render()
+    ).fail((xhr) ->
+      mainMessage = xhr.responseJSON.messages[0]
+      detailedErrors = _.values(xhr.responseJSON.errors)
+      mainMessage.error += detailedErrors
+      Toast.message([mainMessage])
+    )

   onRender : ->


Use consistent date formatting in date helpers

--- app/assets/javascripts/discourse/helpers/application_helpers.js.coffee
+++ app/assets/javascripts/discourse/helpers/application_helpers.js.coffee
@@ -72,8 +72,7 @@

 Handlebars.registerHelper 'unboundDate', (property, options) ->
   dt = new Date(Ember.Handlebars.get(this, property, options))
-  month = Date.SugarMethods.getLocale.method().months[12 + dt.getMonth()]
-  "#{dt.getDate()} #{month}, #{dt.getFullYear()} #{dt.getHours()}:#{dt.getMinutes()}"
+  dt.format("{d} {Mon}, {yyyy} {hh}:{mm}")

 Handlebars.registerHelper 'editDate', (property, options) ->
   dt = Date.create(Ember.Handlebars.get(this, property, options))


Fix submit button on contributions#new

Fix submit button on contributions#new

--- app/assets/javascripts/neighborly/projects/contributions/new.js.coffee
+++ app/assets/javascripts/neighborly/projects/contributions/new.js.coffee
@@ -8,7 +8,7 @@
     el: '.new-contribution-page'

     initialize: ->
-      _.bindAll this, 'resetReward', 'clickReward', 'clickAnonymous'
+      _.bindAll this, 'resetReward', 'clickReward', 'clickAnonymous', 'submitButtonHandler'

       # bind change event (support for ie8 )
       this.$('#contribution_value').change this.resetReward
@@ -21,6 +21,7 @@
       this.selectReward(this.$('input[type=radio]:checked'))
       this.$value.focus()
       this.submitButtonHandler()
+      this.$value.on('keyup', this.submitButtonHandler)

     submitButtonHandler: ->
       if this.$value[0].checkValidity()


always start aniSprite timer

always start aniSprite timer

--- src/aniSprite.coffee
+++ src/aniSprite.coffee
@@ -47,7 +47,7 @@
                 @_dis.current = cycle
         else if what.indexOf("cycle") is 0
             @_dis.cycles ?= new Array()
-            @_dis.timer ?= new Timer()
+            @_dis.timer ?= new Timer(true)

             i = 0
             to.name ?= what.slice 5


Fuck whitespace

Fuck whitespace

--- scripts/steam-info.coffee
+++ scripts/steam-info.coffee
@@ -35,6 +35,6 @@
     else
       $ = cheerio.load(body)
       title = $('title').text()
-      blurb = $('.game_description_snippet').text()
+      blurb = $('.game_description_snippet').text()?.trim() || "I have no fucking idea what '#{title}' is about"
       msg.send title
       msg.send blurb


OX-187 performance testing on server

OX-187 performance testing on server

--- app/assets/javascripts/model/binary/pullqueue.coffee
+++ app/assets/javascripts/model/binary/pullqueue.coffee
@@ -6,8 +6,8 @@
 class PullQueue

   # Constants
-  BATCH_LIMIT : 50
-  BATCH_SIZE : 50
+  BATCH_LIMIT : 1
+  BATCH_SIZE : 1
   ROUND_TRIP_TIME_SMOOTHER : .125
   BUCKET_TIME_SMOOTHER : .125


Workspace: Structural changes for separation of concerns.

--- client/landing/app/CommonViews/workspace/collaborativeworkspace.coffee
+++ client/landing/app/CommonViews/workspace/collaborativeworkspace.coffee
@@ -1,30 +1,33 @@
 class CollaborativeWorkspace extends Workspace

-  constructor: (options = {}, data) ->
-
-    super options, data
-
-    @sessionData    = []
-    instances       = @getOptions().firebaseInstances
-
-    return warn "CollaborativeWorkspace requires at least one Firebase instance." unless instances
-
-    currentInstance = instances
-    currentInstance = instances[KD.utils.getRandomNumber(instances.length) - 1] if Array.isArray instances
-    @firepadRef     = new Firebase "https://#{currentInstance}.firebaseIO.com/"
-    @sessionKey     = options.sessionKey or @createSessionKey()
-    @workspaceRef   = @firepadRef.child @sessionKey
-    @historyRef     = @workspaceRef.child "history"
-    @users          = {}
-
+  init: ->
+    @sessionData = []
+    @users       = {}
+    @createRemoteInstance()
+    @createLoader()
+    @fetchUsers()
     @createUserListContainer()
-    @createLoader()
-
-    if @getOptions().enableChat
-      @container.addSubView @chatView = new ChatPane
-        delegate: this
-      @chatView.hide()
-
+    @createChat()
+    @bindRemoteEvents()
+
+  createChat: ->
+    return unless @getOptions().enableChat
+    @container.addSubView @chatView = new ChatPane
+      delegate: this
+    @chatView.hide()
+
+  createRemoteInstance: ->
+    instanceName  = @getOptions().firebaseInstance
+
+    unless instanceName
+      return warn "CollaborativeWorkspace requires a Firebase instance."
+
+    @firepadRef   = new Firebase "https://#{instanceName}.firebaseIO.com/"
+    @sessionKey   = @getOptions().sessionKey or @createSessionKey()
+    @workspaceRef = @firepadRef.child @sessionKey
+    @historyRef   = @workspaceRef.child "history"
+
+  bindRemoteEvents: ->
     @workspaceRef.once "value", (snapshot) =>
       if @getOptions().sessionKey
         unless snapshot.val()
@@ -72,8 +75,6 @@
       paneSessionKeys = []
       paneSessionKeys.push pane.sessionKey for pane in panes
       @sessionData.push paneSessionKeys
-
-    @fetchUsers()

   fetchUsers: ->
     @workspaceRef.once "value", (snapshot) =>


Use normalized variables instead of original parameters

--- src/app/range.coffee
+++ src/app/range.coffee
@@ -31,9 +31,9 @@
   # columnDelta - A {Number} indicating how far from the starting {Point} the range's column should be
   #
   # Returns the new {Range}.
-  @fromPointWithDelta: (point, rowDelta, columnDelta) ->
-    pointA = Point.fromObject(point)
-    pointB = new Point(point.row + rowDelta, point.column + columnDelta)
+  @fromPointWithDelta: (pointA, rowDelta, columnDelta) ->
+    pointA = Point.fromObject(pointA)
+    pointB = new Point(pointA.row + rowDelta, pointA.column + columnDelta)
     new Range(pointA, pointB)

   # Creates a new `Range` object based on two {Point}s.


don't call notifications

don't call notifications

--- server-ce/settings.coffee
+++ server-ce/settings.coffee
@@ -389,6 +389,8 @@
 			user: httpAuthUser
 			pass: httpAuthPass
 	references:undefined
+	notifications:undefined
+
 	# 	documentupdater:
 	# 		url : "http://localhost:#{docUpdaterPort}"
 	# 	clsi:


added custom footer for email option

added custom footer for email option

--- server-ce/settings.coffee
+++ server-ce/settings.coffee
@@ -469,6 +469,10 @@
 			secure: parse(process.env["SHARELATEX_EMAIL_SMTP_SECURE"])
 			ignoreTLS: parse(process.env["SHARELATEX_EMAIL_SMTP_IGNORE_TLS"])

+
+		templates:
+			customFooter: process.env["SHARELATEX_CUSTOM_EMAIL_FOOTER"]
+
 	if process.env["SHARELATEX_EMAIL_SMTP_USER"]? or process.env["SHARELATEX_EMAIL_SMTP_PASS"]?
 		settings.email.parameters.auth =
 			user: process.env["SHARELATEX_EMAIL_SMTP_USER"]
@@ -477,6 +481,7 @@
 	if process.env["SHARELATEX_EMAIL_SMTP_TLS_REJECT_UNAUTH"]?
 		settings.email.parameters.tls =
 			rejectUnauthorized: parse(process.env["SHARELATEX_EMAIL_SMTP_TLS_REJECT_UNAUTH"])
+

 # Password Settings
 # -----------


Fix typo

Fix typo

--- husl.coffee
+++ husl.coffee
@@ -119,7 +119,7 @@
   else
     c / 12.92

-# Represents rgb [0-1] values as [0-225] values. Errors out if value
+# Represents rgb [0-1] values as [0-255] values. Errors out if value
 # out of the range
 rgbPrepare = (tuple) ->
   tuple = (round(n, 3) for n in tuple)


Organisations: fix description

Organisations: fix description

It was auf Deutsch. Fixes second bullet of #1867.

--- app/assets/javascripts/app/models/organization.coffee
+++ app/assets/javascripts/app/models/organization.coffee
@@ -17,10 +17,10 @@
   ]

   @description = '''
-Mit **Organisationen** können Sie Kunden **gruppieren**. Dies hat u. a. zwei bedeutende Vorteile.
+Using **Organisations** you can **group** customers. This has among others two important benefits:

-1. Als **Agent** haben Sie nicht nur die Übersicht über die Tickets eines Kunden sondern zusätzlich die **Übersicht über die gesamte Organisation**. Z. B. über die Suchen nach der Organisation, diese per einfachen klick öffnen.
-2. Als **Kunde** können Sie die **Tickets ihrer Kollegen mit einsehen** und bearbeiten (sofern die Organisation eine "teilende" ist, dies können Sie je Organisation als Parameter einstellen).
+1. As an **Agent** you do not only have an overview of the open tickets for one person but an **overview over their whole organisation**.
+2. As a **Customer** you can also check the **Tickets which your colleagues created** and modify their tickets (if your organization is set to "shared", which can be defined per organization)).
 '''

   uiUrl: ->


remove hard-coded transcribe tasks

remove hard-coded transcribe tasks

--- app/assets/javascripts/components/dynamic-router.cjsx
+++ app/assets/javascripts/components/dynamic-router.cjsx
@@ -6,30 +6,6 @@
 ImageSubjectViewer_mark = require('./image-subject-viewer-mark')
 ImageSubjectViewer_transcribe = require('./image-subject-viewer-transcribe')

-
-transcribe_steps = [
-    {
-      key: 0,
-      type: 'date', # type of input
-      field_name: 'date',
-      label: 'Date',
-      instruction: 'Please type-in the log date.'
-    },
-    {
-      key: 1,
-      type: 'text',
-      field_name: 'journal_entry',
-      label: 'Journal Entry',
-      instruction: 'Please type-in the journal entry for this day.'
-    },
-    {
-      key: 2,
-      type: 'textarea',
-      field_name: 'other_entry',
-      label: 'Other Entry',
-      instruction: 'Type something, anything.'
-    }
-]
 pages = [
   {
     name:    'info',


Update he.js.coffee

Update he.js.coffee

--- app/assets/javascripts/uploadcare/locale/he.js.coffee
+++ app/assets/javascripts/uploadcare/locale/he.js.coffee
@@ -21,7 +21,13 @@
     buttons:
       cancel: 'ביטול'
       remove: 'הסר'
-      file: 'מחשב'
+      choose:
+        files:
+          one: 'בחר קובת'
+          other: 'בחר קבצים'
+        images:
+          one: 'בחר תמונה'
+          other: 'בחר תמונות'
     dialog:
       done: 'סיום'
       showFiles: 'הצג קבצים'


rmv un-used  injection

rmv un-used  injection

--- src/plugins/spark-scroll-gsap.coffee
+++ src/plugins/spark-scroll-gsap.coffee
@@ -78,6 +78,6 @@
     @

-angular.module('gilbox.sparkScroll').factory 'sparkAnimator', ($document) ->
+angular.module('gilbox.sparkScroll').factory 'sparkAnimator', ->
   instance: ->
     new GSAPAnimator()


Tweaked command usage in github-issue-link

--- src/scripts/github-issue-link.coffee
+++ src/scripts/github-issue-link.coffee
@@ -12,8 +12,9 @@
 #   HUBOT_GITHUB_API
 #
 # Commands:
-#   Listens for #nnn or repo-name#nnn and links to the issue on github.
-#   Defaults to issues in HUBOT_GITHUB_REPO, unless a repo is specified
+#   #nnn - link to GitHub issue nnn for HUBOT_GITHUB_REPO project
+#   repo#nnn - link to GitHub issue nnn for repo project
+#   user/repo#nnn - link to GitHub issue nnn for user/repo project
 #
 # Notes:
 #   HUBOT_GITHUB_API allows you to set a custom URL path (for Github enterprise users)


#925 - a user can cancel the adding new question process with escape or click

--- jsapp/xlform_model_view/view.rowSelector.coffee
+++ jsapp/xlform_model_view/view.rowSelector.coffee
@@ -45,6 +45,13 @@
       @line.css "height", "inherit"
       @line.html $viewTemplates.$$render('xlfRowSelector.namer')
       $.scrollTo @line, 200, offset: -300
+      $(window).on 'keydown.cancel_add_question',  (evt) =>
+        if evt.which == 27
+          @hide()
+
+      $('body').on 'mousedown.cancel_add_question', (evt) =>
+        if $(evt.target).closest('.line.expanded').length == 0
+          @hide()

     show_picker: (evt) ->
       evt.preventDefault()
@@ -73,6 +80,9 @@
     hide: ->
       @button.removeClass('btn--hidden')
       @line.empty().removeClass("expanded").css "height": 0
+      $(window).off 'keydown.cancel_add_question'
+      $('body').off 'mousedown.cancel_add_question'
+      @line.parents(".survey-editor__null-top-row").removeClass "expanded"

     selectMenuItem: (evt)->
       @question_name = @line.find('input').val()


make loading of jupyter notebooks more robust

--- src/smc-webapp/editor_jupyter.coffee
+++ src/smc-webapp/editor_jupyter.coffee
@@ -138,13 +138,14 @@
         # wait until connected -- iT is ***critical*** to wait until
         # the kernel is connected before doing anything else!
         start = new Date()
-        max_time_ms = 30*1000 # try for up to 30s
+        max_time_ms = 15*1000 # try for up to 15s
         f = () =>
             @frame ?= window.frames[@iframe_uuid]
             if not @frame
                 setTimeout(f, 250)
                 return
-            if new Date() - start >= max_time_ms
+            innerHTML = @frame?.document?.body?.innerHTML
+            if (new Date() - start >= max_time_ms) or (innerHTML? and innerHTML.indexOf('
504 Gateway Time-out
') != -1)
                 @state = 'error'
                 @error = 'timeout loading'
                 cb(@error)
@@ -172,11 +173,13 @@
                         @dirty_interval = setInterval(@check_dirty, 250)
                         @nb.events.on('spec_changed.Kernel', => @nb.dirty = true)
                         @init_cursor()
-                    @monkey_patch_frame()
-                    @disable_autosave()
-                    @state = 'ready'
-                    @emit('ready')
-                    cb()
+                    f = () =>
+                        @monkey_patch_frame()
+                        @disable_autosave()
+                        @state = 'ready'
+                        @emit('ready')
+                        cb()
+                    setTimeout(f, 1000)  # give it a second "for luck"
                 else
                     # not yet connected, so try again shortly
                     setTimeout(f, 250)
@@ -805,24 +808,31 @@
                 cb(err)

     init_dom: (cb) =>
-        # We try twice, since the first time may result in a timeout or gateway
+        if @state != 'loading'
+            cb("init_dom BUG: @state must be loading")
+            return
+        # We try multiple times, since result maybe be a timeout or gateway
         # error as Jupyter gets started up.
-        @_init_dom (err) =>
-            if not err
-                cb()
-            else
-                console.warn("Jupyter -- error loading first time so will try again -- #{err}")
-                @dom?.close()
-                @_init_dom(cb)
+        misc.retry_until_success
+            f        : @_init_dom
+            max_time : 4*60*1000  # try for at most 4 minutes
+            cb       : (err) =>
+                if not err
+                    cb()
+                else
+                    console.warn("Jupyter -- failed to load -- #{err}")
+                    @dom?.close()
+                    cb(err)

     _init_dom: (cb) =>
         if @state != 'loading'
             cb("init_dom BUG: @state must be loading")
             return
-        @notebook.css('opacity',.05)  # so you don't see a funny half-loaded notebook (from the file rather than the syncstring), before monkey patching.
+        @notebook.css('opacity',0)  # invisible, so you don't see a funny half-loaded notebook (from the file rather than the syncstring), before monkey patching.
         done = (err) =>
             @notebook.css('opacity',1)
             if err
+                @dom?.close()
                 cb(err)
             else
                 if @dom.read_only


Move Image to start of form

Move Image to start of form

--- src/components/exercise.cjsx
+++ src/components/exercise.cjsx
@@ -75,11 +75,11 @@

         Exercise Number: {ExerciseStore.getNumber(id)}
+        <ImageChooser exerciseId={id} />

         Exercise Stimulus
         <textarea onChange={@updateStimulus} defaultValue={ExerciseStore.getStimulus(id)}>
         </textarea>
-        <ImageChooser exerciseId={id} />

       {questions}


Use forceUpdate instead of empty setState

--- src/components/exercise.cjsx
+++ src/components/exercise.cjsx
@@ -16,7 +16,7 @@
   exerciseId:   React.PropTypes.string.isRequired

   getInitialState: -> {}
-  update: -> @setState({})
+  update: -> @forceUpdate()

   componentWillMount: ->
     ExerciseStore.addChangeListener(@update)


Fixed missing ] in names.coffee.

Fixed missing ] in names.coffee.

--- app/lib/world/names.coffee
+++ app/lib/world/names.coffee
@@ -1,41 +1,117 @@
 module.exports.thangNames = thangNames =
   'Soldier M': [
+    'Aaron'
+    'Adam'
+    'Alan'
+    'Albert'
     'Alistair'
+    'Andrew'
+    'Anthony'
+    'Antonio'
+    'Arthur'
     'Augustus'
     'Barron'
+    'Benjamin'
     'Bill'
+    'Billy'
+    'Bobby'
     'Bond'
+    'Brandon'
+    'Brian'
+    'Bruce'
+    'Carl'
+    'Carlos'
     'Cecil'
+    'Charles'
+    'Chris'
+    'Christopher'
     'Cid'
+    'Clarence'
+    'Craig'
+    'Daniel'
+    'David'
     'Dax'
+    'Dennis'
+    'Donald'
+    'Douglas'
     'Duke'
+    'Earl'
+    'Edward'
+    'Eric'
+    'Ernest'
+    'Eugene'
     'Ezra'
     'Felix'
     'Ferb'
+    'Frank'
+    'Fred'
+    'Gary'
     'Gatsby'
+    'George'
+    'Gerald'
     'Gordon'
+    'Gregory'
     'Halle'
+    'Harold'
     'Harry'
+    'Henry'
     'Hirium'
+    'Howard'
     'Huburt'
     'Hugo'
+    'Jack'
+    'James'
+    'Jason'
     'Jax'
+    'Jeffrey'
+    'Jeremy'
+    'Jerry'
+    'Jesse'
+    'Jimmy'
+    'Joe'
+    'John'
+    'Johnny'
     'Jonah'
+    'Jonathan'
+    'Jose'
+    'Joseph'
+    'Joshua'
+    'Juan'
+    'Justin'
+    'Keith'
+    'Kenneth'
+    'Kevin'
     'Kirin'
     'Kumar'
+    'Larry'
+    'Lawrence'
     'Leopold'
+    'Louis'
     'Lucas'
     'Lucian'
     'Malcolm'
     'Marcus'
+    'Mark'
+    'Martin'
+    'Matthew'
     'Max'
     'Maxwell'
+    'Michael'
     'Miles'
     'Mischa'
+    'Nicholas'
     'Orion'
+    'Patrick'
+    'Paul'
+    'Peter'
+    'Philip'
     'Philippian'
+    'Phillip'
     'Phineas'
     'Pierce'
+    'Ralph'
+    'Randy'
+    'Raymond'
     'Remy'
     'Ricardo'
     'Richard'
@@ -44,40 +120,154 @@
     'Roger'
     'Ronald'
     'Ronan'
+    'Roy'
+    'Russell'
     'Ryan'
     'Sage'
     'Samson'
+    'Samuel'
+    'Scott'
+    'Sean'
+    'Shawn'
     'Silas'
+    'Stephen'
     'Sterling'
+    'Steve'
+    'Steven'
     'Stormy'
+    'Terry'
     'Thelonious'
     'Theo'
+    'Thomas'
+    'Timothy'
+    'Todd'
     'Tyrone'
+    'Victor'
+    'Walter'
+    'Wayne'
     'William'
+    'Willie'
   ]
   'Soldier F': [
     'Alana'
     'Alexandra'
+    'Alice'
     'Allankrita'
+    'Amanda'
+    'Amy'
+    'Andrea'
+    'Angela'
+    'Ann'
+    'Anna'
+    'Anne'
     'Annie'
     'Aphrodite'
+    'Ashley'
+    'Barbara'
+    'Betty'
+    'Beverly'
+    'Bonnie'
+    'Brenda'
     'Buffy'
+    'Carol'
+    'Carolyn'
+    'Catherine'
+    'Cheryl'
+    'Christina'
+    'Christine'
     'Coco'
+    'Cynthia'
+    'Deborah'
+    'Debra'
+    'Denise'
+    'Diana'
+    'Diane'
+    'Donna'
+    'Doris'
+    'Dorothy'
+    'Elizabeth'
+    'Emily'
+    'Evelyn'
+    'Frances'
     'Gabrielle'
+    'Gloria'
     'Gorgin'
+    'Heather'
+    'Helen'
     'Helga'
     'Holly'
     'Imani'
+    'Irene'
+    'Jacqueline'
+    'Jane'
+    'Janet'
+    'Janice'
+    'Jean'
+    'Jennifer'
+    'Jessica'
     'Joan'
+    'Joyce'
+    'Judith'
+    'Judy'
+    'Julia'
+    'Julie'
+    'Karen'
+    'Katherine'
+    'Kathleen'
+    'Kathryn'
+    'Kathy'
     'Kay'
+    'Kelly'
+    'Kimberly'
     'Lana'
+    'Laura'
+    'Lillian'
+    'Linda'
+    'Lisa'
+    'Lois'
+    'Lori'
+    'Louise'
     'Lukaz'
+    'Margaret'
+    'Maria'
+    'Marie'
+    'Marilyn'
+    'Martha'
+    'Mary'
+    'Melissa'
+    'Michelle'
+    'Mildred'
+    'Nancy'
     'Natasha'
+    'Nicole'
     'Nikita'
+    'Norma'
+    'Pamela'
+    'Patricia'
+    'Paula'
+    'Phyllis'
+    'Rachel'
+    'Rebecca'
+    'Robin'
+    'Rose'
+    'Ruby'
+    'Ruth'
+    'Sandra'
+    'Sara'
     'Sarah'
     'Scarlett'
     'Shannon'
+    'Sharon'
+    'Shirley'
+    'Stephanie'
+    'Susan'
+    'Tammy'
+    'Teresa'
+    'Theresa'
+    'Tina'
     'Trinity'
+    'Virginia'
+    'Wanda'
   ]
   'Peasant M': [
     'Azgot'


Removed superfluous variable

Removed superfluous variable

--- app/models/provider.coffee
+++ app/models/provider.coffee
@@ -35,8 +35,7 @@
   ).property('otherNamePrefix', 'otherFirstName', 'otherMiddleName', 'otherLastName', 'otherNameSuffix')

   classifcationSpecialties: ( ->
-    taxonomies = @get('taxonomyLicenses')
-    taxonomies.map((license)->
+    @get('taxonomyLicenses').map((license)->
       license.get('taxonomyCode').get('classificationSpecialty')
     ).uniq()
   ).property('taxonomyLicenses.@each.taxonomyCode.classificationSpecialty')


Refactor

Refactor

--- src/contexts/progress.coffee
+++ src/contexts/progress.coffee
@@ -35,19 +35,19 @@

 Player = React.createClass
-    render: ->
-      @PlayerState = PlayerState
-      require("jade-react!../components/Player.jade") @
+  render: ->
+    @PlayerState = PlayerState
+    require("jade-react!../components/Player.jade") @

 JudgePanel = React.createClass
-   render: ->
-      require("jade-react!../components/JudgePanel.jade") @
+  render: ->
+    require("jade-react!../components/JudgePanel.jade") @

 ViewControlPanel = React.createClass
-   render: ->
-      require("jade-react!../components/ViewControlPanel.jade") @
+  render: ->
+    require("jade-react!../components/ViewControlPanel.jade") @

 ProgressComponent = React.createClass


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/collision.coffee
+++ src/collision.coffee
@@ -216,7 +216,7 @@
     ###
     rayRectangle: (source, direction, target) ->
       unless target.xw? and target.yw?
-        return Collision.ra
+        return Collision.rayRectangl
       xw = target.xw
       yw = target.yw


Trigger step event earlier

Trigger step event earlier

--- src/core/aghs.coffee
+++ src/core/aghs.coffee
@@ -192,16 +192,17 @@
     'id':             null
   }

-  # called before the user-defined render
-
+  @time = () -> return time
+
+  # the internal step function, private to Aghs::start
+  # called before the step event
   step = (e) ->
     return unless @running is true
-    @events.trigger "step", time
     _now = now()
     time.now = _now
     time.delta = (_now - time.lastCalled)
     time.elapsed += time.delta
-
+    @events.trigger "step", time

     if skipFrame(time.delta)
       @config.frameskip.count += 1


More concise IIFE

More concise IIFE

--- src/core/loop.coffee
+++ src/core/loop.coffee
@@ -1,4 +1,4 @@
-((window, Elyssa) ->
+do (window = @, Elyssa = @Elyssa or= {}) ->

   # Elyssa.Loop
   # Static class
@@ -101,6 +101,4 @@
       delete taskList[taskName] if taskList[taskName]

       @
-
-
-)(@, @Elyssa or= {})
+


avoid loading unused, unavailable libraries

--- src/custodian.coffee
+++ src/custodian.coffee
@@ -2,16 +2,19 @@
 Documentstore = require './documentstore'

 StorageEngines =
-  levelup: require './storage-engine/levelup'
-  memory: require './storage-engine/memory'
-  rest: require './storage-engine/rest'
+  levelup:
+    -> require './storage-engine/levelup'
+  memory:
+    -> require './storage-engine/memory'
+  rest:
+    -> require './storage-engine/rest'

 class Custodian
   constructor: (@config={}) ->
     if !(@ instanceof Custodian) then return new Custodian(@config)

     storageEngine = @config?.storage ? 'memory'
-    storageEngine = StorageEngines[storageEngine] if (typeof storageEngine is 'string')
+    storageEngine = StorageEngines[storageEngine]() if (typeof storageEngine is 'string')
     @store = new storageEngine(@config)

     @data = Datastore(@store.data)


Add a 1 second delay to the smoke tests (#81)

* Add a 1 second delay to the smoke tests

Fixes a race condition where smoke tests exit before container can be attached to.

See here for more info: https://github.com/overleaf/sharelatex/issues/274

* give the smoke tests additional work to do

* escape slashes

--- services/clsi/test/smoke/coffee/SmokeTests.coffee
+++ services/clsi/test/smoke/coffee/SmokeTests.coffee
@@ -17,10 +17,37 @@
 					resources: [
 						path: "main.tex"
 						content: """
-							\\documentclass{article}
-							\\begin{document}
-							Hello world
-							\\end{document}
+% Membrane-like surface
+% Author: Yotam Avital
+\\documentclass{article}
+\\usepackage{tikz}
+\\usetikzlibrary{calc,fadings,decorations.pathreplacing}
+\\begin{document}
+\\begin{tikzpicture}
+  \\def\\nuPi{3.1459265}
+  \\foreach \\i in {5,4,...,2}{% This one doesn't matter
+    \\foreach \\j in {3,2,...,0}{% This will crate a membrane
+                               % with the front lipids visible
+      % top layer
+      \\pgfmathsetmacro{\\dx}{rand*0.1}% A random variance in the x coordinate
+      \\pgfmathsetmacro{\\dy}{rand*0.1}% A random variance in the y coordinate,
+                                     % gives a hight fill to the lipid
+      \\pgfmathsetmacro{\\rot}{rand*0.1}% A random variance in the
+                                      % molecule orientation
+      \\shade[ball color=red] ({\\i+\\dx+\\rot},{0.5*\\j+\\dy+0.4*sin(\\i*\\nuPi*10)}) circle(0.45);
+      \\shade[ball color=gray] (\\i+\\dx,{0.5*\\j+\\dy+0.4*sin(\\i*\\nuPi*10)-0.9}) circle(0.45);
+      \\shade[ball color=gray] (\\i+\\dx-\\rot,{0.5*\\j+\\dy+0.4*sin(\\i*\\nuPi*10)-1.8}) circle(0.45);
+      % bottom layer
+      \\pgfmathsetmacro{\\dx}{rand*0.1}
+      \\pgfmathsetmacro{\\dy}{rand*0.1}
+      \\pgfmathsetmacro{\\rot}{rand*0.1}
+      \\shade[ball color=gray] (\\i+\\dx+\\rot,{0.5*\\j+\\dy+0.4*sin(\\i*\\nuPi*10)-2.8}) circle(0.45);
+      \\shade[ball color=gray] (\\i+\\dx,{0.5*\\j+\\dy+0.4*sin(\\i*\\nuPi*10)-3.7}) circle(0.45);
+      \\shade[ball color=red] (\\i+\\dx-\\rot,{0.5*\\j+\\dy+0.4*sin(\\i*\\nuPi*10)-4.6}) circle(0.45);
+    }
+  }
+\\end{tikzpicture}
+\\end{document}
 						"""
 					]
 		}, (@error, @response, @body) =>


Whitespace-induced bug

Whitespace-induced bug

--- src/core/ProgramState.coffee
+++ src/core/ProgramState.coffee
@@ -43,7 +43,7 @@
 		@inputPointer++
 		ret
 	else
-	0
+		0

 ProgramState::put = (e, y, x, currentX, currentY, currentDir, index) ->


Refactor & update dashboard to show a districts layer

--- app/scripts/mapctrl.coffee
+++ app/scripts/mapctrl.coffee
@@ -6,21 +6,30 @@
     $scope.choroChoice = "percFun"

     # FIXME: "ward" and "region" should be defined elsewhere I think
-    # NOTE: Rewritten to avoid too much "ward" / "region" lookup
-    getFeatureType = (feature) ->
-      return if feature.properties.hasOwnProperty "REGNAME" then "region" else "ward"
-
     getFeaturedItem = (feature) ->
-      itemType = getFeatureType(feature)
-      nameProperty = if itemType == "region" then "REGNAME" else "Ward_Name"
-      name = feature.properties[nameProperty].toLowerCase()
-      item = $scope[itemType + "Map"][name]
-      [name, item, itemType]
-
-    getItem = (feature) ->
-      featureType = getFeatureType(feature)
-      return [featureType, getFeaturedItem(feature)[1]]
-
+      res = {}
+      props = feature.properties
+
+      if props.hasOwnProperty "Region_Nam"
+        res.type = "region"
+        res.name = props.Region_Nam
+        res.code = +props.Region_Cod
+      else if props.hasOwnProperty "Ward_Name"
+        res.type = "ward"
+        res.name = props.Ward_Name
+        res.code = +props.Ward_Code
+      else if props.hasOwnProperty "District_N"
+        res.type = "district"
+        res.name = props.District_N
+        res.code = +props.District_C
+      else
+        throw new Error("Unknown geo layer")
+
+      # FIXME: looking up by name which not correct. Should really be by
+      # code by this causes issues down the line as the population data
+      # does not contain any codes
+      res.item = $scope[res.type + "Map"][res.name.toLowerCase()]
+      res

     initMap = (waterpoints, mapCenter) ->

@@ -28,12 +37,12 @@
       ### EVENT HANDLERS ###
       ######################
       mouseOver = (e) ->
-        [name, item, itemType] = getFeaturedItem(e.target.feature)
-
-        if item
-          hoverText = item[itemType + "_name"] + ": " + item[$scope.choroChoice].toPrecision(3) + "%"
+        it = getFeaturedItem(e.target.feature)
+
+        if it.item
+          hoverText = it.name + ": " + it.item[$scope.choroChoice].toPrecision(3) + "%"
         else
-          hoverText = name + ": unknown"
+          hoverText = it.name + ": unknown"

         $scope.$apply (scope) ->
           scope.hoverText = hoverText
@@ -45,9 +54,9 @@
           fillOpacity: 0.8

       onClick = (e) ->
-        [name, item, itemType] = getFeaturedItem(e.target.feature)
-        if item
-          $scope.drillDown(item[itemType + "_name"], itemType + "_name", true)
+        it = getFeaturedItem(e.target.feature)
+        if it.item
+          $scope.drillDown(it.item[it.type + "_name"], it.type + "_name", true)

       ##############
       ### STYLES ###
@@ -57,8 +66,8 @@
                     .range(["red","orange","green"])

       style = (feature) ->
-        [type, item] = getItem(feature)
-        color = unless item then "gray" else colScale(item[$scope.choroChoice])
+        it = getFeaturedItem(feature)
+        color = unless it.item then "gray" else colScale(it.item[$scope.choroChoice])

         s =
           fillColor: color
@@ -133,11 +142,13 @@
       ################
       $q.all([
         getTopoJsonLayer("data/tz_regions.topojson", "tz_regions", true)
+        getTopoJsonLayer("data/tz_districts.topojson", "tz_districts", false)
         getTopoJsonLayer("data/tz_wards.topojson", "tz_wards", false)
       ]).then((data) ->

-        [regions, regionLayer] = data[0]
-        [wards, wardLayer]     = data[1]
+        [regions, regionLayer]    = data[0]
+        [districts, districtLayer]= data[1]
+        [wards, wardLayer]        = data[2]

         ################
         ### MAKE MAP ###
@@ -148,6 +159,7 @@

         overlayControls =
           "Regions": regionLayer
+          "Districts": districtLayer
           "Wards": wardLayer

         map = L.map('nationalDashMap',
@@ -165,16 +177,17 @@
         $scope.$watch('choroChoice', (val) ->
           return unless val

+          layers = [regionLayer, districtLayer, wardLayer]
+
+          layers.forEach (l) ->
+            l.setStyle(style)
+            if map.hasLayer(l)
+              map.removeLayer(l)
+              map.addLayer(l)
+
           regionLayer.setStyle(style)
+          districtLayer.setStyle(style)
           wardLayer.setStyle(style)
-
-          if map.hasLayer(regionLayer)
-            map.removeLayer(regionLayer)
-            map.addLayer(regionLayer)
-
-          if map.hasLayer(wardLayer)
-            map.removeLayer(wardLayer)
-            map.addLayer(wardLayer)
         )

         $scope.$watch('params.region', (val) ->
@@ -183,9 +196,9 @@

           # only 26 regions so a simple linear search is ok
           for f in regions
-            r = f.properties.REGNAME.toLowerCase()
-
-            if r == val.toLowerCase()
+            it = getFeaturedItem(f)
+
+            if it.name.toLowerCase() == val.toLowerCase()
               # I don't really understand why this works, but it does...
               # FIXME if you can!
               numToUnpack = 2
@@ -193,9 +206,11 @@
                 coordsToUse = f.geometry.coordinates
               else
                 coordsToUse = [f.geometry.coordinates]
+              console.log coordsToUse
               points = L.GeoJSON.coordsToLatLngs(coordsToUse, numToUnpack)
               # instantiate as multipolygon to get the bounds
               bounds = L.multiPolygon(points).getBounds()
+              console.log bounds
               map.fitBounds(bounds)
               return
         )
@@ -210,8 +225,9 @@

     # Get the boundaries and layers
     $q.all([
+      # FIXME: assumes unique names which is not the case
       waterpointStats.getStats(null, null, null, "region_name", true)
-      # FIXME: relies on the fact that wards are uniquely named
+      waterpointStats.getStats(null, null, null, "district_name", true)
       waterpointStats.getStats(null, null, null, "ward_name", true)
     ]).then((data) ->
       # Add the regions and wards to the template scope
@@ -221,7 +237,8 @@

       # data contains stats for region and ward
       addToScope(data[0], "region")
-      addToScope(data[1], "ward")
+      addToScope(data[1], "district")
+      addToScope(data[2], "ward")

       # Initialise the map
       initMap([], new L.LatLng(-6.3153, 35.15625))


update #branch to return the upstream branch name

--- lib/github-file.coffee
+++ lib/github-file.coffee
@@ -113,4 +113,5 @@

   # Internal
   branch: ->
-    @repo.getShortHead()
+    refName = @repo.getUpstreamBranch() # e.g., "refs/remotes/origin/master"
+    refName?.match(/^refs\/remotes\/.*\/(.*)$/)?[1] or @repo.getShortHead()


:fire: Don't trigger lint on pane change

--- lib/linter-plus.coffee
+++ lib/linter-plus.coffee
@@ -27,8 +27,6 @@
     @subscriptions.add atom.config.observe 'linter.lintOnFly', (value) =>
       @lintOnFly = value
     @subscriptions.add atom.project.onDidChangePaths =>
-      @commands.lint()
-    @subscriptions.add atom.workspace.onDidChangeActivePaneItem =>
       @commands.lint()

     @subscriptions.add atom.workspace.observeTextEditors (editor) =>


添加Single-Sync/server

添加Single-Sync/server

--- single-sync/server.coffee
+++ single-sync/server.coffee
@@ -0,0 +1,57 @@
+# # # # # # # # # # # # # # # # # # # #
+# server.coffee
+# # # # # # # # # # # # # # # # # # # #
+
+const = require("const")
+file = require("file_util")
+net = require("net_util")
+
+$rootPath = ""
+
+serverHelloWorld = (request, response) ->
+    response.write("Hello world !")
+    response.end()
+    return
+
+serverSyncStart = (request, response) ->
+    allArray = scanPath($rootPath)
+    deleteArray = listDeletePath($rootPath, allArray)
+    for deletePath in deleteArray
+        deleteFile(deletePath)
+    updateArray = listUpdatePath($rootPath, updateArray)
+    response.write({
+        status: "OK"
+        updateArray: updateArray
+    })
+    response.end()
+    return
+
+serverSyncData = (request, response) ->
+    subPath = request.getHeader("matcha_path")
+    if not subPath
+        response.setHeader("motcha_status", "ERR")
+        response.setHeader("motcha_message", "Miss field path")
+        response.end()
+        return
+    writeStream = writeFile($rootPath, subPath)
+    request.pipe(writeStream)
+    writeStream.on "end", () ->
+        response.setHeader("motcha_status", "OK")
+    return
+
+serverEntry = (request, response) ->
+    path = url.parse(request.url, true)
+    switch path.pathname
+        when "/start"
+            httpStart(request, response)
+        when "/data"
+            fileSyncData(request, response)
+        when "/end"
+            fileSyncEnd(request, response)
+        when "/cancel"
+            fileSyncCancel(request, response)
+    return
+
+server = new http.Server()
+server.on("request", serverEntry)
+server.listen(NET_PORT)


Update TODO

Update TODO

--- lib/script-view.coffee
+++ lib/script-view.coffee
@@ -100,7 +100,7 @@
       @selectedText = editor.getText()

     # If we still don't have selected text, use the path
-    # TODO: Will probably bork on empty string
+    # TODO: Totally borks on empty string, valid lang
     if (not @selectedText? or not @selectedText)
       console.log("Down the file path")
       @filepath = editor.getPath()


Catch errors when launching process.

Catch errors when launching process.

--- lib/script-view.coffee
+++ lib/script-view.coffee
@@ -153,11 +153,22 @@
     stdout = (output) => @display("stdout", output)
     stderr = (output) => @display("stderr", output)
     exit = (return_code) =>
-      if return_code is 0 then @headerView.setStatus("stop") else @headerView.setStatus("err")
+      if return_code is 0
+        @headerView.setStatus("stop")
+      else
+        @headerView.setStatus("err")
       console.log "Exited with #{return_code}"

     # Run process
-    @bufferedProcess = new BufferedProcess({command, args, options, stdout, stderr, exit})
+    @bufferedProcess = new BufferedProcess({command, args, options,
+                                            stdout, stderr, exit})
+    @bufferedProcess.process.on('error', (node_error) =>
+      @output.append("
Unable to run
")
+      @output.append("
#{_.escape(command)}
")
+      @output.append("
Is it on your path?
")
+      @output.append("
PATH: #{_.escape(process.env.PATH)}
")
+
+    )

   stop: ->
     # Kill existing process if available


hides link in status update title when it is either at the start or at the end. will not hide stuff surrounded by text OR when the status update is just the link

--- client/app/Applications/Activity.kdapplication/ListItems/ActivityListItemStatusUpdate.coffee
+++ client/app/Applications/Activity.kdapplication/ListItems/ActivityListItemStatusUpdate.coffee
@@ -61,7 +61,17 @@
     if $(event.target).is("[data-paths~=body]")
       appManager.tell "Activity", "createContentDisplay", @getData()

-  applyTextExpansions:(str = "")-> @utils.applyTextExpansions str, yes
+  applyTextExpansions:(str = "")->
+    link = @getData().link?.link_url
+    if link
+      isJustLink = str.trim() is link
+      endsWithLink = str.trim().indexOf(link, str.trim().length - link.length) isnt -1
+      startsWithLink = str.trim().indexOf(link) is 0
+
+      if not isJustLink and (endsWithLink or startsWithLink)
+        str = str.replace link, ""
+
+    @utils.applyTextExpansions str, yes

   pistachio:->
     """


Replace `unless` with plain `if`.

Replace `unless` with plain `if`.

--- resources/assets/coffee/_classes/react-turbolinks.coffee
+++ resources/assets/coffee/_classes/react-turbolinks.coffee
@@ -21,7 +21,7 @@
       for own _name, component of @components
         continue if component.loaded

-        continue unless component.target.length
+        continue if component.target.length == 0

         component.loaded = true
         ReactDOM.render React.createElement(component.element, component.propsFunction()), component.target[0]
@@ -29,7 +29,7 @@

     $(document).on 'turbolinks:before-cache', =>
       for own _name, component of @components
-        continue unless component.loaded
+        continue if !component.loaded

         component.loaded = false
         ReactDOM.unmountComponentAtNode component.target[0]


fixed copying svgs and what not

fixed copying svgs and what not

--- grunt/contrib-copy.coffee
+++ grunt/contrib-copy.coffee
@@ -5,7 +5,8 @@
           src: [
             'index.html'
             'assets/font/*'
-            'assets/img/*.png'
+            'assets/img/*.{gif,png,svg}'
+            'assets/img/**/*.jpg'
             'assets/partial/**/*.html'
           ]
           dest: 'build/'


added right, bottom, max-width

added right, bottom, max-width

--- bokehjs/src/coffee/renderer/tile/tile_renderer.coffee
+++ bokehjs/src/coffee/renderer/tile/tile_renderer.coffee
@@ -36,11 +36,11 @@
       if @attributionEl?
         @attributionEl.html(attribution)
       else
-        border_width = 1
-
+        border_width = @map_plot.get('outline_line_width').value
         bottom_offset = @map_plot.get('min_border_bottom') + border_width
         right_offset = @map_plot.get('min_border_right') + border_width
-
+        right_offset = @map_plot.get('min_border_right') + border_width
+        max_width = @map_frame.get('width') - border_width
         @attributionEl = $('<div>')
           .html(attribution)
           .addClass('bk-tile-attribution')
@@ -48,11 +48,10 @@
             'position': 'absolute'
             'bottom': "#{bottom_offset}px"
             'right': "#{right_offset}px"
+            'max-width': "#{max_width}px"
             'background-color': 'rgba(255,255,255,0.8)'
             'font-size': '9pt'
             'font-family': 'sans-serif'
-            'padding-left': '2px'
-            'padding-right': '2px'
           })

         overlays = @plot_view.$el.find('div.bk-canvas-overlays')


New clone method

New clone method

--- source/core/helper.coffee
+++ source/core/helper.coffee
@@ -41,20 +41,22 @@

   # Private Methods
   _clone: (obj) ->
-    return obj if not obj? or typeof obj isnt 'object'
-    return new Date(obj.getTime()) if obj instanceof Date
+    return JSON.parse(JSON.stringify(obj))

-    if obj instanceof RegExp
-      flags = ''
-      flags += 'g' if obj.global?
-      flags += 'i' if obj.ignoreCase?
-      flags += 'm' if obj.multiline?
-      flags += 'y' if obj.sticky?
-      return new RegExp(obj.source, flags)
+    # return obj if not obj? or typeof obj isnt 'object'
+    # return new Date(obj.getTime()) if obj instanceof Date

-    newInstance = new obj.constructor()
-    newInstance[key] = @_clone obj[key] for key of obj
-    newInstance
+    # if obj instanceof RegExp
+    #   flags = ''
+    #   flags += 'g' if obj.global?
+    #   flags += 'i' if obj.ignoreCase?
+    #   flags += 'm' if obj.multiline?
+    #   flags += 'y' if obj.sticky?
+    #   return new RegExp(obj.source, flags)
+
+    # newInstance = new obj.constructor()
+    # newInstance[key] = @_clone obj[key] for key of obj
+    # newInstance

   mix       : mix
   isArray   : isArray


Started working on the enchanced list functionality where a list can keep child controllers instead of rendering a partial. This enables a list to manage internal transitions better as well as listen for realtime updates to the models without having to rerender everything.

--- list.coffee
+++ list.coffee
@@ -23,9 +23,35 @@
 	prepare: ->

 	render:(collection) ->
-		@html @template(collection)								# Render HAML view. Requires the wrapping Array for some reason.
-		#console.log @el										# TODO: Fix this so that the list works with other templating engines.
-
+		if @template
+			# Render HAML view. Requires the wrapping Array for some reason.
+			# TODO: Fix this so that the list works with other templating engines.
+			@html @template(collection)
+		else if @controller
+			# Dynamically create child controllers
+			for item in collection
+				child = @getOrCreateChild item
+
+			@deactivateAndKillOrphans(@getChildren(), collection)
+			@orderChildren(@getChildren(), collection)
+
+	getOrCreateChild: (item) ->
+		child = @getChildById(item.id)
+		unless child
+			child = new @controller
+			child.id = item.id
+			@addChild child
+			child.prepareWithModel item
+			@append child
+
+		return child
+
+	# Find children that have been deleted from the collection. Deactivate them, remove them from the DOM and make them available for GC.
+	deactivateAndKillOrphans: (children, collection) ->
+
+	# Reorder the children in the DOM according to the collection order.
+	orderChildren: (children, collection) ->
+
 	click: (e) ->
 		item = $(e.currentTarget).item()
 		@trigger('change', item, $(e.currentTarget))


Update recursion.coffee

Update recursion.coffee

--- server/materials/data/topics/recursion.coffee
+++ server/materials/data/topics/recursion.coffee
@@ -7,7 +7,8 @@
     return {
         topic: topic("Рекурсия", "Задачи на рекурсию", [
             label("Теория на фоксфорде"),
-            label("""Имейте в виду, что многие из задач ниже на самом деле проще и правильнее решать не рекурсией, а циклом (в частности, никогда не пишите факториал рекурсией!).
+            label("""Имейте в виду, что многие из задач ниже на самом деле проще и правильнее решать не рекурсией, а циклом
+            (в частности, никогда не пишите факториал или числа Фибоначчи рекурсией!).
             Но понимать, что такое рекурсия, надо, и очень полезно потренироваться на задачах ниже.""")
             problem(153),
             problem(154)
@@ -21,4 +22,4 @@
             problem(3806),
             problem(111589)
         ]
-    }+    }


redirect to vocab editor if exercise matches

--- src/stores/location.coffee
+++ src/stores/location.coffee
@@ -57,5 +57,14 @@
   partsForView: (view = @getCurrentUrlParts().view) ->
     VIEWS[view] or VIEWS['search']

+  # callback for when a record is newly loaded from store
+  # Location may choose to redirect to a different editor depending on the data
+  onRecordLoad: (id, store) ->
+    {view} = @getCurrentUrlParts()
+    record = store.get(id)
+    # use vocab editor
+    if view is 'exercises' and record.vocab_term_uid
+      @visitVocab(record.vocab_term_uid)
+

 module.exports = Location


activity: add smooth deletion for activity item

--- client/Social/Activity/views/activitylistitem.coffee
+++ client/Social/Activity/views/activitylistitem.coffee
@@ -5,7 +5,7 @@
   constructor:(options = {}, data)->

     options.type               = 'activity'
-    options.cssClass           = KD.utils.curry 'activity-item status', options.cssClass
+    options.cssClass           = KD.utils.curry 'activity-item status fade-out', options.cssClass
     options.commentViewClass or= CommentView
     options.commentSettings  or= {}
     options.attributes       or= {}
@@ -86,8 +86,10 @@

   initViewEvents: ->

-    @settingsButton.on 'ActivityIsDeleted',     @bound 'delete'
-    @settingsButton.on 'ActivityEditIsClicked', @bound 'showEditWidget'
+    @settingsButton.on 'ActivityDeleteStarted'  , @bound 'hide'
+    @settingsButton.on 'ActivityDeleteSucceded' , @bound 'delete'
+    @settingsButton.on 'ActivityDeleteFailed'   , @bound 'show'
+    @settingsButton.on 'ActivityEditIsClicked'  , @bound 'showEditWidget'

   initDataEvents: ->
@@ -153,13 +155,6 @@
     list.emit 'EditMessageReset'

-  delete: ->
-
-    @emit 'ActivityIsDeleted'
-    list.removeItem this
-    @destroy()
-
-
   # setAnchors: ->

   #   @$("article a").each (index, element) ->
@@ -224,8 +219,36 @@
   partial:-> ''

-  hide:-> @setClass   'hidden-item'
-  show:-> @unsetClass 'hidden-item'
+  hide: ->
+
+    @isBeingHidden = yes
+    @setClass 'out'
+    @setCss 'margin-top', "-#{@getHeight()}px"
+
+    KD.utils.wait 347 + 300, =>
+      @emit 'HideAnimationFinished'
+      @setClass 'hidden'
+      @isBeingHidden = no
+
+
+  show: -> @whenSubmitted().then => @unsetClass 'hidden out'
+
+
+  whenSubmitted: ->
+    new Promise (resolve) =>
+      if @isBeingHidden
+      then @once 'HideAnimationFinished', -> resolve()
+      else resolve()
+
+
+  delete: ->
+
+    @whenSubmitted().then =>
+      list = @getDelegate()
+      @emit 'ActivityIsDeleted'
+      list.removeItem this
+      @destroy()
+

   render : ->
     super


Activitiy: Input Widget: Remove unused parameter.

--- client/activity/lib/views/activityinputwidget.coffee
+++ client/activity/lib/views/activityinputwidget.coffee
@@ -231,7 +231,7 @@
   getEmbedBoxPayload: -> return @embedBox.getData()

-  showError: (err, options = {}) ->
+  showError: (err) ->

     showError err
     @unlockSubmit()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_game_state.coffee
+++ src/engine_game_state.coffee
@@ -37,7 +37,7 @@
   add: (classNameOrEntityData, entityData={}) ->
     # Allow optional add "Class", data form
     if classNameOrEntityData.isString?()
-      entityData.class = className
+      entityData.class = classNameOrEntityData
     else
       entityData = className


Changed default avatar url

Changed default avatar url

--- client/Main/CommonViews/avatarviews/avatarview.coffee
+++ client/Main/CommonViews/avatarviews/avatarview.coffee
@@ -75,7 +75,7 @@
     # and you need to upload them to koding-cdn/images bucket.
     # Thanks to gravatar to not support svg's, damn.

-    defaultAvatarUri = "https://koding-cdn.s3.amazonaws.com/images/default.avatar.#{size}.png"
+    defaultAvatarUri = "https://koding-cdn.s3.amazonaws.com/square-avatars/default.avatar.#{size}.png"
     return "//gravatar.com/avatar/#{profile.hash}?size=#{size}&d=#{defaultAvatarUri}&r=g"

   render: ->


Client: fat arrow fixes

Client: fat arrow fixes

--- client/app/lib/maincontroller/groupscontroller.coffee
+++ client/app/lib/maincontroller/groupscontroller.coffee
@@ -73,7 +73,7 @@

   openSocialGroupChannel:(group, callback=->) ->
     {realtime, socialapi} = kd.singletons
-    socialapi.channel.byId { id: group.socialApiChannelId }, (err, channel) ->
+    socialapi.channel.byId { id: group.socialApiChannelId }, (err, channel) =>
       return callback  if err

       subscriptionData =
@@ -83,7 +83,7 @@
         token      : channel.token
         channelId  : group.socialApiChannelId

-      realtime.subscribeChannel subscriptionData, (err, groupChan) ->
+      realtime.subscribeChannel subscriptionData, (err, groupChan) =>
         return callback err  if err

         @filterXssAndForwardEvents groupChan, [


Cleaned up device model, annotated future abstractions

--- client/mobile/app/coffee/modules/device/model.coffee
+++ client/mobile/app/coffee/modules/device/model.coffee
@@ -3,6 +3,7 @@
 # # # # #

 # Observes connection and sends leave-behind alerts
+# TODO - should this be part of the Bluetooth service?
 class ConnectionObserver extends Marionette.Object

   initialize: (options) =>
@@ -64,6 +65,8 @@
   write: (data) =>
     Backbone.Radio.channel('bluetooth').request('write', @, data)

+  # # # # #
+  # TODO - abstract into Device.Keyboard
   writeKeyup: =>
     @write([2,0,0,0,0,0,0])

@@ -86,6 +89,18 @@

     @write([2,shift,charMap[char.toLowerCase()],0,0,0,0])

+  writeChar: (char) =>
+    @writeKeydown(char)
+    @writeKeyup()
+
+  sendText: (text = 'lyrebird') =>
+    @writeChar(char) for char in text
+    return true
+  #
+  # # # # #
+
+  # # # # #
+  # TODO - abstract into Device.Mouse
   clickMouseLeft: =>
     @write([1,0,0,1])
     @write([1,0,0,0])
@@ -94,16 +109,10 @@
     @write([1,0,0,2])
     @write([1,0,0,0])

-  writeChar: (char) =>
-    @writeKeydown(char)
-    @writeKeyup()
-
   writeMousePos: (pos) =>
     @write([1,pos.x,pos.y,0])
-
-  sendText: (text = 'lyrebird') =>
-    @writeChar(char) for char in text
-    return true
+  #
+  # # # # #

 # # # # #


Fix tests

Fix tests

--- test/authority-test.coffee
+++ test/authority-test.coffee
@@ -9,11 +9,18 @@
     @robot =
       respond: sinon.spy()
       hear: sinon.spy()
+      listenerMiddleware: sinon.spy (context, next, done) =>
+          # Do nothing

     require("../src/authority")(@robot)

   it "registers a respond listener", ->
-    expect(@robot.respond).to.have.been.calledWith(/hello/)
+    expect(@robot.respond).to.have.been.calledWith(/auth me/i)
+    expect(@robot.respond).to.have.been.calledWith(/auth block (.+) (.+)/i)
+    expect(@robot.respond).to.have.been.calledWith(/auth unblock (.+) (.+)/i)
+    expect(@robot.respond).to.have.been.calledWith(/auth assign (.+) (.+)/i)
+    expect(@robot.respond).to.have.been.calledWith(/auth unassign (.+) (.+)/i)
+    expect(@robot.respond).to.have.been.calledWith(/auth default (.+)/i)

-  it "registers a hear listener", ->
-    expect(@robot.hear).to.have.been.calledWith(/orly/)+  it "registers a listener middleware", ->
+    expect(@robot.listenerMiddleware).to.have.been.called


FIX: Remove typo in can apply for extension code

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

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

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


Now testing correct copy of jQuery in Auth spec

--- test/spec/readmill/auth.coffee
+++ test/spec/readmill/auth.coffee
@@ -1,4 +1,5 @@
 describe "Auth", ->
+  jQuery = Annotator.$
   Auth = Annotator.Readmill.Auth
   auth = null


more precise test on return value of emitter methods

--- test/specs/kefir-stream.coffee
+++ test/specs/kefir-stream.coffee
@@ -112,6 +112,18 @@
     deactivate(a)
     expect(emitter.emit(1)).toBe(false)

+    a = Kefir.stream (em) ->
+      expect(em.emit(1)).toBe(true)
+      expect(em.emit(2)).toBe(false)
+      expect(em.emit(3)).toBe(false)
+    lastX = null
+    f = (x) ->
+      lastX = x
+      if x == 2
+        a.offValue(f)
+    a.onValue(f)
+    expect(lastX).toBe(2)
+
   it 'emitter should have methods `value` and `event`', ->
     expect(
       Kefir.stream (em) ->


Implement Logout

Implement Logout

Signed-off-by: Ian Macalinao <08370e385f4778cad8fe504ec5445edd3d45bd9a@gmail.com>

--- config/routes.coffee
+++ config/routes.coffee
@@ -7,6 +7,8 @@
   router.get '/login', 'login'
   router.post '/login', 'login#index_post'

+  router.get '/logout', 'logout'
+
   router.get '/bans', 'bans'
   router.get '/bans/:page', 'bans'
   router.get '/bans/show/:id', 'bans#show'


Minor fixes.

Minor fixes.

--- services/web/public/coffee/directives/creditCards.coffee
+++ services/web/public/coffee/directives/creditCards.coffee
@@ -137,7 +137,6 @@
 				value   = $target.val()
 				value   = replaceFullWidthChars(value)
 				value   = value.replace(/\D/g, '')
-				safeVal(value, $target)

 		# Format Card Number
 		reFormatCardNumber = (e) ->
@@ -146,7 +145,6 @@
 				value   = $target.val()
 				value   = replaceFullWidthChars(value)
 				value   = $.payment.formatCardNumber(value)
-				#safeVal(value, $target)

 		formatCardNumber = (e) ->
 			# Only format if input is a number
@@ -155,7 +153,7 @@

 			$target = $(e.currentTarget)
 			value   = $target.val()
-			card    = ccUtils.cardFromNumber(value + digit)
+			card    = ccUtils.fromNumber(value + digit)
 			length  = (value.replace(/\D/g, '') + digit).length

 			upperLength = 16
@@ -202,6 +200,26 @@
 				e.preventDefault()
 				setTimeout -> $target.val(value.replace(/\d$/, ''))

+		getFormattedCardNumber = (num) ->
+			num = num.replace(/\D/g, '')
+			card = ccUtils.fromNumber(num)
+			return num unless card
+
+			upperLength = card.length[card.length.length - 1]
+			num = num[0...upperLength]
+
+			if card.format.global
+				num.match(card.format)?.join(' ')
+			else
+				groups = card.format.exec(num)
+				return unless groups?
+				groups.shift()
+				groups = $.grep(groups, (n) -> n) # Filter empty groups
+				groups.join(' ')
+
+		parseCardNumber = (value) ->
+    		if value? then value.replace(/\s/g, '') else value
+
 		# Format Expiry
 		reFormatExpiry = (e) ->
 			$target = $(e.currentTarget)
@@ -209,7 +227,6 @@
 				value   = $target.val()
 				value   = replaceFullWidthChars(value)
 				value   = $.payment.formatExpiry(value)
-				safeVal(value, $target)

 		formatExpiry = (e) ->
 			# Only format if input is a number
@@ -275,8 +292,6 @@
 			if value?
 				dateAsObj = ccUtils.parseExpiry(value)

-				console.log dateAsObj
-
 				return unless dateAsObj?

 				expiry = new Date dateAsObj.year, dateAsObj.month - 1
@@ -290,7 +305,6 @@
 				value   = $target.val()
 				value   = replaceFullWidthChars(value)
 				value   = value.replace(/\D/g, '')[0...4]
-				safeVal(value, $target)

 		# Restrictions
 		restrictNumeric = (e) ->
@@ -320,7 +334,7 @@

 			# Restrict number of digits
 			value = ($target.val() + digit).replace(/\D/g, '')
-			card  = cardFromNumber(value)
+			card  = ccUtils.fromNumber(value)

 			if card
 				value.length <= card.length[card.length.length - 1]
@@ -372,6 +386,8 @@
 			reFormatCardNumber
 			formatCardNumber
 			formatBackCardNumber
+			getFormattedCardNumber
+			parseCardNumber
 			reFormatExpiry
 			formatExpiry
 			formatForwardExpiry
@@ -386,18 +402,41 @@
 			setCardType
 		}

-	App.directive 'ccFormatExpiry', (ccFormat) ->
-		restrict: 'A'
-		require: 'ngModel'
+	App.directive "ccFormatExpiry", (ccFormat) ->
+		restrict: "A"
+		require: "ngModel"
 		link: (scope, el, attrs, ngModel) ->
-			el.on 'keypress', 	ccFormat.restrictExpiry
-			el.on 'keypress', 	ccFormat.formatExpiry
-			el.on 'keypress', 	ccFormat.formatForwardSlash
-			el.on 'keypress', 	ccFormat.formatForwardExpiry
-			el.on 'keydown', 	ccFormat.formatBackExpiry
+			el.on "keypress", 	ccFormat.restrictExpiry
+			el.on "keypress", 	ccFormat.formatExpiry
+			el.on "keypress", 	ccFormat.formatForwardSlash
+			el.on "keypress", 	ccFormat.formatForwardExpiry
+			el.on "keydown", 	ccFormat.formatBackExpiry

 			ngModel.$parsers.push ccFormat.parseExpiry
 			ngModel.$formatters.push ccFormat.parseExpiry

+	App.directive "ccFormatCardNumber", (ccFormat) ->
+		restrict: "A"
+		require: "ngModel"
+		link: (scope, el, attrs, ngModel) ->
+			el.on "keypress", 	ccFormat.restrictCardNumber
+			el.on "keypress", 	ccFormat.formatCardNumber
+			el.on "keydown", 	ccFormat.formatBackCardNumber
+			el.on "paste", 		ccFormat.reFormatCardNumber
+
+			ngModel.$parsers.push ccFormat.parseCardNumber
+			ngModel.$formatters.push ccFormat.getFormattedCardNumber
+
+	App.directive "ccFormatSecCode", (ccFormat) ->
+		restrict: "A"
+		require: "ngModel"
+		link: (scope, el, attrs, ngModel) ->
+			el.on "keypress", 	ccFormat.restrictNumeric
+			el.on "keypress", 	ccFormat.restrictCVC
+			el.on "paste", 		ccFormat.reFormatCVC
+			el.on "change", 	ccFormat.reFormatCVC
+			el.on "input", 		ccFormat.reFormatCVC
+
+


bootstrap courses on roster

bootstrap courses on roster

--- tutor/specs/components/course_settings/roster.spec.cjsx
+++ tutor/specs/components/course_settings/roster.spec.cjsx
@@ -1,7 +1,7 @@
 {React, Testing, pause, sinon, _, ReactTestUtils} = require '../helpers/component-testing'
 ld = require 'lodash'
 Roster = require '../../../src/components/course-settings/roster'
-
+{ bootstrapCoursesList } = require('../../courses-test-data')
 COURSE = require '../../../api/user/courses/1.json'
 ROSTER = require '../../../api/courses/1/roster.json'

@@ -14,6 +14,7 @@
 describe 'Course Settings', ->

   beforeEach ->
+    bootstrapCoursesList()
     CourseActions.loaded(COURSE, COURSE_ID)
     RosterActions.loaded(ROSTER, COURSE_ID)
     sinon.stub(PeriodActions, 'delete', (periodId) ->


Require just the necessary JS files for Branch tests

Requiring the whole application is not recommended because it can be slow to
compile all the assets and may hide not wanted dependencies.

--- spec/javascripts/repository/branch_spec.js.coffee
+++ spec/javascripts/repository/branch_spec.js.coffee
@@ -1,4 +1,6 @@
-#= require application
+#= require jquery
+#= require repository
+#= require repository/branch
 #= require sinon

 describe "Branch#constructor", ->


FIX: drawArea wasn't updating for bitmaps - and probalby other things - when parent's size changed

--- source/Art/Engine/Elements/AtomElement.coffee
+++ source/Art/Engine/Elements/AtomElement.coffee
@@ -13,6 +13,8 @@
   constructor: ->
     super
     @_drawOptions = {}
+
+  getSizeAffectsDrawArea: -> true

   @drawProperty
     color:


add commands description for build-trigger

--- src/dockerhub.coffee
+++ src/dockerhub.coffee
@@ -11,6 +11,10 @@
 # Commands:
 #   hubot dockerhub notify <repo> to <room> -- DockerHub の <repo> の Web Hook の通知を <room> に設定
 #   hubot dockerhub notify show -- DockerHub の Web Hook の通知先 room を表示
+#   hubot dockerhub trigger set <repo> with <token> -- DockerHub の <repo> の build trigger のトークン <token> を設定
+#   hubot dockerhub trigger del <repo> -- <repo> の build trigger の設定を削除
+#   hubot dockerhub trigger show -- DockerHub の build trigger の設定を表示
+#   hubot dockerhub trigger invoke <repo> -- DockerHub の <repo> の build trigger を発火
 #
 # URLS:
 #   POST /hubot/dockerhub/notify


Pathy turtle

Pathy turtle

--- src/draw_grid.coffee
+++ src/draw_grid.coffee
@@ -49,8 +49,10 @@
     path = "M#{x},#{y} "
     for offset in [[1,1], [1,-1], [-1,1], [-1,-1]]
       path += draw_ellipse(width, offset[0], offset[1])
-    breadth = cell_size * 2 / 3
-    path += "m#{width},0 " + draw_ellipse(breadth, -3 / 2,0)
+    head = cell_size / 4
+    path += "m#{width / 3},0 " + draw_ellipse(head, 2,0)
+    shell = cell_size * 2 / 3
+    path += "m#{width / 2},0 " + draw_ellipse(shell , -3 / 2,0)
     S.path {class: "path-turtle", d: path, fill: "#88ff88"}


Change some spec stuff

Change some spec stuff

--- spec/commands-spec.coffee
+++ spec/commands-spec.coffee
@@ -14,7 +14,10 @@
     editor = atom.workspace.buildTextEditor()
     editor.setText('')
     editor.setCursorBufferPosition(0, 0)
-    # TODO set width of tab to 2 characters, and only use soft-tabs
+
+    editor.config.set('editor.softTabs', true)
+    editor.config.set('editor.tabLength', 2)
+    editor.config.set('editor.tabType', 'soft')

     runs ->
       grammar = atom.grammars.grammarForScopeName('text.md')
@@ -64,11 +67,10 @@
       atom.commands.dispatch(editorElement, "markdown:indent-list-item")
       expect(editor.getText()).toBe('    - item')

-    # TODO How does indenting work with tabs instead of spaces?
-    xit 'indents an tabbed indented list-item', ->
+    it 'indents a tabbed indented list-item', ->
       editor.setText('\t- item')
       atom.commands.dispatch(editorElement, "markdown:indent-list-item")
-      expect(editor.getText()).toBe('\t\t- item')
+      expect(editor.getText()).toBe('  \t- item')

     it 'does NOT indent an invalid list-item', ->
       editor.setText('-item')
@@ -84,7 +86,7 @@
       editor.setText('```\n- item\n```')
       editor.setCursorBufferPosition(1, 3)
       atom.commands.dispatch(editorElement, "markdown:indent-list-item")
-      expect(editor.getText()).not.toBe('```\n  - item\n```')
+      expect(editor.getText()).toBe('```\n- item\n```')

     it 'indents a valid numbered list-item', ->
       editor.setText('1. item')
@@ -97,10 +99,10 @@
       expect(editor.getText()).toBe('  - [ ] task')

     # TODO it should not indent definition-lists
-    xit 'does NOT indent definition-lists', ->
-      editor.setText(': definition')
-      atom.commands.dispatch(editorElement, "markdown:indent-list-item")
-      expect(editor.getText()).toBe(': definition')
+    # xit 'does NOT indent definition-lists', ->
+    #   editor.setText(': definition')
+    #   atom.commands.dispatch(editorElement, "markdown:indent-list-item")
+    #   expect(editor.getText()).toBe(': definition')

   # describe 'outdenting list-items', ->


Add test for new display setting

Add test for new display setting

--- spec/mru-list-spec.coffee
+++ spec/mru-list-spec.coffee
@@ -4,6 +4,8 @@

 describe 'MRU List', ->
   workspaceElement = null
+  enableMruConfigKey = 'tabs.enableMruTabSwitching'
+  displayMruTabListConfigKey = 'tabs.displayMruTabList'

   beforeEach ->
     workspaceElement = atom.workspace.getElement()
@@ -86,8 +88,16 @@
       fourthActiveItem = pane.getActiveItem()
       expect(fourthActiveItem).toBe(firstActiveItem)

+    it "disables display when configured to", ->
+      atom.config.set(displayMruTabListConfigKey, false)
+      expect(atom.config.get(displayMruTabListConfigKey)).toBe(false)
+      if pane.onChooseNextMRUItem?
+        expect(pane.getItems().length).toBe 2
+        atom.commands.dispatch(workspaceElement, 'pane:show-next-recently-used-item')
+        expect(workspaceElement.querySelectorAll('.tabs-mru-switcher li').length).toBe 0
+
+
   describe "config", ->
-    configKey = 'tabs.enableMruTabSwitching'
     dotAtomPath = null

     beforeEach ->
@@ -100,7 +110,8 @@
       fs.removeSync(dotAtomPath)

     it "defaults on", ->
-      expect(atom.config.get(configKey)).toBe(true)
+      expect(atom.config.get(enableMruConfigKey)).toBe(true)
+      expect(atom.config.get(displayMruTabListConfigKey)).toBe(true)

       bindings = atom.keymaps.findKeyBindings(
         target: document.body,
@@ -127,7 +138,7 @@
       expect(bindings[0].command).toBe('pane:move-active-item-to-top-of-stack')

     it "alters keybindings when disabled", ->
-      atom.config.set(configKey, false)
+      atom.config.set(enableMruConfigKey, false)
       bindings = atom.keymaps.findKeyBindings(
         target: document.body,
         keystrokes: 'ctrl-tab')


:green_heart: Fix broken package deactivation tests on Travis

--- spec/pigments-spec.coffee
+++ spec/pigments-spec.coffee
@@ -42,7 +42,7 @@
         spyOn(project, 'destroy').andCallThrough()
         spyOn(buffer, 'destroy').andCallThrough()

-        atom.packages.deactivatePackage('pigments')
+        pigments.deactivate()

     it 'destroys the pigments project', ->
       expect(project.destroy).toHaveBeenCalled()


Conserta bug na geração de uniqueDimensionsMap

--- src/coffee/portal-sku-selector.coffee
+++ src/coffee/portal-sku-selector.coffee
@@ -16,7 +16,8 @@
 		@skus = skus

 		#Create dimensions map
-		@selectedDimensionsMap = (undefined for dimension in dimensions)
+		@selectedDimensionsMap = {}
+		@selectedDimensionsMap[dimension] = undefined for dimension in dimensions

 		@uniqueDimensionsMap = @findUniqueDimensions()

@@ -118,7 +119,7 @@
 				.removeClass('checked sku-picked')

 			# Enable all selectable options in this row
-			for value of @uniqueDimensionsMap[dimension]
+			for value in @uniqueDimensionsMap[dimension]
 				# Search for the sku dimension value corresponding to this dimension
 				for sku in selectableSkus
 					skuDimensionValue = sku.dimensions[dimension]
@@ -170,6 +171,7 @@
 		dimensionValue = $(this).attr('data-value')
 		console.log 'Change dimension!', dimensionName, dimensionValue
 		skuSelectorObj.selectedDimensionsMap[dimensionName] = dimensionValue
+		console.log(skuSelectorObj.selectedDimensionsMap)
 		skuSelectorObj.resetNextDimensions(dimensionName)
 		selectedSku = skuSelectorObj.findSelectedSku()
 		undefinedDimensions = skuSelectorObj.findUndefinedDimensions()


Substitui window.location.origin por protocol + host

--- src/coffee/portal-sku-selector.coffee
+++ src/coffee/portal-sku-selector.coffee
@@ -68,9 +68,7 @@
 	$.get '/api/catalog_system/pub/products/variations/' + productId

 $.skuSelector.getAddUrlForSku = (sku, seller = 1, qty = 1, redirect = true) ->
-	protocol = if window.location.host.indexOf('vtexlocal') isnt -1 then 'http' else 'https'
-	# protocol + '://' + window.location.host + "/checkout/cart/add?qty=#{qty}&seller=#{seller}&sku=#{sku}&redirect=#{redirect}"
-	window.location.origin + "/checkout/cart/add?qty=#{qty}&seller=#{seller}&sku=#{sku}&redirect=#{redirect}"
+	window.location.protocol + '//' + window.location.host + "/checkout/cart/add?qty=#{qty}&seller=#{seller}&sku=#{sku}&redirect=#{redirect}"

 # Creates the DOM of the Sku Selector, with the appropriate event bindings
 $.skuSelector.createSkuSelector = (productId, name, dimensions, skus, options, $el) =>


fix crop to specified size when selection is not changed

--- app/assets/javascripts/uploadcare/ui/crop/crop-widget.js.coffee
+++ app/assets/javascripts/uploadcare/ui/crop/crop-widget.js.coffee
@@ -19,7 +19,6 @@
         createDragbars: []
         bgColor: 'transparent'
         bgOpacity: .8
-

       @setCrop(crop)
       @setSelection()
@@ -89,7 +88,6 @@
       width: Math.round(Math.min(@originalSize[0], coords.x2)) - left
       height: Math.round(Math.min(@originalSize[1], coords.y2)) - top

-
     getSelectionWithModifiers: ->
       coords = @getSelection()
       {width: w, height: h} = coords
@@ -97,15 +95,15 @@
       modifiers = ''

       if w isnt @originalSize[0] or h isnt @originalSize[1]
-        modifiers = "-/crop/#{w}x#{h}/#{coords.left},#{coords.top}/"
+        modifiers += "-/crop/#{w}x#{h}/#{coords.left},#{coords.top}/"

-        downscale = @crop.downscale and (w > prefered[0] or h > prefered[1])
-        upscale = @crop.upscale and (w < prefered[0] or h < prefered[1])
-        if downscale or upscale
-          [coords.sw, coords.sh] = prefered
-          modifiers += "-/resize/#{prefered.join 'x'}/"
-        else
-          modifiers += "-/preview/"
+      downscale = @crop.downscale and (w > prefered[0] or h > prefered[1])
+      upscale = @crop.upscale and (w < prefered[0] or h < prefered[1])
+      if downscale or upscale
+        [coords.sw, coords.sh] = prefered
+        modifiers += "-/resize/#{prefered.join 'x'}/"
+      else
+        modifiers += "-/preview/"

       crop: coords
       modifiers: modifiers


Make sure the caching_controller doesn't define onShow & onClose

This gives us the ability to define the onShow and onClose in inherited
controllers, like the conversations_controller.

--- core/app/backbone/factlink/caching_controller.coffee
+++ core/app/backbone/factlink/caching_controller.coffee
@@ -1,6 +1,12 @@
 class Backbone.Factlink.CachingController extends Backbone.Factlink.BaseController
-  onShow:   -> @cached_views = new Backbone.Factlink.DetachedViewCache
-  onClose:  -> @cached_views.cleanup()
+  openController: ->
+    super
+    @cached_views = new Backbone.Factlink.DetachedViewCache
+
+  closeController: ->
+    super
+    @cached_views.cleanup()
+
   onAction: -> @unbindFrom @permalink_event if @permalink_event?

   makePermalinkEvent: (baseUrl=null)->


Add test for repeating indent operations

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

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


Keep the generated ID, just use the prefix

--- src/javascripts/embed.coffee
+++ src/javascripts/embed.coffee
@@ -52,7 +52,7 @@

     hsh(string.charCodeAt(i)) for i in [0..string.length]

-    return hash.toString(16).substring(1)
+    "pdg-#{hash.toString(16).substring(1)}"

   origin: () ->
     scriptSrc = @elem.src || @elem.getAttribute('src')


Flesh out the SideView tests.

Flesh out the SideView tests.

--- spec/side-view-spec.coffee
+++ spec/side-view-spec.coffee
@@ -2,16 +2,41 @@

 describe 'SideView', ->

-  it 'triggers conflict resolution', ->
+  side = null
+  view = null
+  line = null
+
+  beforeEach ->
+    line = {}
     side = {
-      resolve: -> null,
       klass: -> 'klass',
       site: -> 99,
-      description: -> ''
+      description: -> '',
+      lines: -> [line]
     }
+    view = new SideView(side)
+
+  it 'triggers conflict resolution', ->
+    side.resolve = -> null
     spyOn(side, "resolve")

-    view = new SideView(side)
     view.useMe()

     expect(side.resolve).toHaveBeenCalled()
+
+  describe 'when chosen as the resolution', ->
+
+    beforeEach ->
+      side.wasChosen = -> true
+      line.addClass = -> null
+
+    it 'adds the "resolved" class'
+    it 'deletes the marker line'
+
+  describe 'when not chosen as the resolution', ->
+
+    beforeEach ->
+      side.wasChosen = -> true
+
+    it 'deletes its hunks lines'
+    it ''


add test for #666

add test for #666

--- spec/specs/holdwhen.coffee
+++ spec/specs/holdwhen.coffee
@@ -85,6 +85,16 @@
             holdWhen(left.map(true).startWith(true).mapEnd(false))
         left.merge(bufferedRight)
       [1, 2, 3, 4, 5, 6], unstable)
+  describe "Ends after flush if source has ended while holding, with never-ending valve", ->
+    expectStreamEvents(
+      ->
+        src = series(2, [1, 2])
+        valveSource = later(5, false).startWith(true)
+        valve = new Bacon.Bus()
+        valve.plug(valveSource)
+        src.holdWhen(valve)
+      [1, 2], unstable)
+
   describe "In combination with .toProperty", ->
     expectPropertyEvents(
       -> later(1,1).holdWhen(Bacon.constant(false)).toProperty(0)


Resolve references in FilePage

Resolve references in FilePage

Fixes #29

--- src/file-page.coffee
+++ src/file-page.coffee
@@ -43,6 +43,6 @@
       console.log("Generating header: #{name}")
       contents = "# #{name}\n#{contents}"

-    @markdownify(contents)
+    @markdownify(@resolveReferences(contents))

 module.exports = FilePage


Disable URL corruption by TinyMCE

Disable URL corruption by TinyMCE

--- app/assets/javascripts/rails_admin/custom/ui.coffee
+++ app/assets/javascripts/rails_admin/custom/ui.coffee
@@ -5,3 +5,4 @@
     tinymce.init
       selector:'.tinymce-editable',
       plugins: "image link table media print charmap preview code"
+      convert_urls: false


Don't require corpora to define languages

--- app/assets/javascripts/rglossa/models/corpus.coffee
+++ app/assets/javascripts/rglossa/models/corpus.coffee
@@ -11,22 +11,22 @@
     languages = @getLanguages(corpus)

     if languageCode is 'single'  # monolingual corpus
-      languages[0]
+      if languages.length then languages[0] else null
     else
       (language in languages where language.lang is languageCode)?[0]

   getLabels: (corpus, languageCode = 'single') ->
     language = @getLanguage(corpus, languageCode)
-    language.displayAttrs or corpus.displayAttrs or []
+    language?.displayAttrs or corpus.displayAttrs or []

   getPOSAttribute: (corpus, languageCode = 'single') ->
     language = @getLanguage(corpus, languageCode)
-    language.tags?.attr or 'pos'
+    language?.tags?.attr or 'pos'

   getTags: (corpus, languageCode = 'single') ->
     language = @getLanguage(corpus, languageCode)
-    language.tags
+    language?.tags or []

   isMultilingual: (corpus) ->
     @getLanguages(corpus).length > 1