SocialWorker: JReward: replace sinkrow with async

--- workers/social/lib/social/models/rewards/index.coffee
+++ workers/social/lib/social/models/rewards/index.coffee
@@ -4,6 +4,7 @@
 JAccount    = require '../account'
 JUser       = require '../user'

+async       = require 'async'
 { argv }    = require 'optimist'
 KONFIG      = require('koding-config-manager').load("main.#{argv.c}")

@@ -11,7 +12,7 @@

   { Relationship } = jraphical

-  { race, secure, daisy, dash, signature, ObjectId } = require 'bongo'
+  { secure, signature, ObjectId } = require 'bongo'

   @share()

@@ -144,7 +145,7 @@

     queue = [

-      ->
+      (next) ->

         reward = new JReward {
           amount         : campaign.perEventAmount
@@ -153,20 +154,19 @@
           originId, type, unit
         }

-        reward.save (err) ->
-          return callback err  if err
-          queue.next()
-
-      ->
+        reward.save next
+
+      (next) ->

         campaign.increaseGivenAmount (err) ->
           logError "Couldn't increase given amount:", err  if err?
-
-          callback null
+          next()

     ]

-    daisy queue
+    async.series queue, (err) ->
+      return callback err  if err
+      callback null

   logError = (rest...) -> console.error '[Rewards]', rest...
@@ -313,7 +313,7 @@
         rewards ?= []

         rewards.forEach (reward) ->
-          queue.push ->
+          queue.push (next) ->

             JAccount.one { _id: reward.providedBy }, (err, account) ->
               if not err and account
@@ -322,13 +322,10 @@
                 reward.providedBy = null
                 reward._hasError  = new KodingError 'No user found'

-              queue.next()
-
-        queue.push ->
+              next()
+
+        async.series queue, (err) ->
           callback null, { total, rewards }
-
-        daisy queue
-

   # Background Processes
   # --------------------
@@ -349,39 +346,33 @@
       campaign = null

       queue = [
-        ->
-
+
+        (next) ->
           # TODO Add fetcher for active campaign ~ GG
           JRewardCampaign.isValid 'register', (err, res) ->
-            return logError err  if err
+            return next err  if err
             return  unless res.isValid

             campaign = res.campaign
-            queue.next()
-
-        ->
-
+            next()
+
+        (next) ->
           fetchReferrer account, (err, _referrer) ->
-            return logError err  if err
+            return next err  if err
             return  unless _referrer
             referrer = _referrer
-            queue.next()
-
-        ->
-
-          createRewards campaign, referrer, account, (err) ->
-            return logError err  if err
-            queue.next()
-
-        ->
-
-          createRewards campaign, account, referrer, (err) ->
-            return logError err  if err
-            queue.next()
+            next()
+
+        (next) ->
+          createRewards campaign, referrer, account, next
+
+        (next) ->
+          createRewards campaign, account, referrer, next

       ]

-      daisy queue
+      async.series queue, (err) ->
+        logError err  if err

   addEmailConfimedListener = ->
@@ -400,48 +391,43 @@

       queue = [

-        ->
+        (next) ->

           user.fetchOwnAccount (err, myAccount) ->
-            return logError err  if err
+            return next err  if err
             # if account not found then do nothing and return
-            return logError "Account couldn't found" unless myAccount
+            return next "Account couldn't found" unless myAccount

             me = myAccount
-            queue.next()
-
-        ->
+            next()
+
+        (next) ->

           if me.referralUsed
-            return logError 'User already get the referrer'
+            return next 'User already get the referrer'

           fetchReferrer me, (err, _referrer) ->
-            return logError err  if err
+            return next err  if err
             return  unless _referrer
             referrer = _referrer
-            queue.next()
-
-        ->
-
-          me.update { $set: { 'referralUsed': yes } }, (err) ->
-            return logError err if err
-            queue.next()
-
-        ->
-
-          confirmRewards referrer, me, (err) ->
-            return logError err if err
-            queue.next()
-
-        ->
-
-          confirmRewards me, referrer, (err) ->
-            return logError err if err
-            queue.next()
+            next()
+
+        (next) ->
+
+          me.update { $set: { 'referralUsed': yes } }, next
+
+        (next) ->
+
+          confirmRewards referrer, me, next
+
+        (next) ->
+
+          confirmRewards me, referrer, next

       ]

-      daisy queue
+      async.series queue, (err) ->
+        logError err  if err

   do ->


SocialWorker: Tests: export bucket variable

--- workers/social/testhelper/models/accounthelper.coffee
+++ workers/social/testhelper/models/accounthelper.coffee
@@ -1,9 +1,16 @@
-JAppStorage      = require '../../lib/social/models/appstorage'
-{ Relationship } = require 'jraphical'
+JAppStorage              = require '../../lib/social/models/appstorage'
+{ Relationship }         = require 'jraphical'
+{ generateRandomString } = require '../index'

 createOldAppStorageDocument = (data, callback) ->
-  { account, appId, version, bucket } = data
-  bucket ?= {}
+  { account, appId, version } = data
+
+  bucket =
+    someString  : generateRandomString()
+    someData    :
+      moreData  : { data : {} }
+    anotherData :
+      someArray : [1,2,3]

   storage = new JAppStorage { appId, version, bucket }
   storage._shouldPrune = no
@@ -20,7 +27,7 @@

     rel = new Relationship relationshipOptions
     rel.save (err) ->
-      callback err, { storage, relationshipOptions }
+      callback err, { storage, relationshipOptions, bucket }

 module.exports = {


Includes remaining REST methods

Includes remaining REST methods

--- apps/statuses/index.coffee
+++ apps/statuses/index.coffee
@@ -25,3 +25,5 @@
   app
     .get "/statuses/#{code}", send
     .post "/statuses/#{code}", send
+    .put "/statuses/#{code}", send
+    .delete "/statuses/#{code}", send


account: add time to file name to invalidate cache

--- client/Account/views/username.coffee
+++ client/Account/views/username.coffee
@@ -127,17 +127,17 @@
     {mimeType, content} = avatar

     KD.utils.s3upload
-      name    : 'avatar'
+      name    : "avatar-#{Date.now()}"
       content : content
       mimeType: mimeType
       timeout : 30000
-
     , (err, url) =>

       @uploadAvatarBtn.hideLoader()
-      unless err
-        @account.modify "profile.avatar": "#{url}?#{Date.now()}"
-
+
+      return KD.showError err  if err
+
+      @account.modify "profile.avatar": "#{url}"

   update: (formData) ->


fix focus for compose

fix focus for compose

--- client/app/components/compose.coffee
+++ client/app/components/compose.coffee
@@ -129,12 +129,12 @@
                 div className: 'form-group',
                     div className: classInput,
                         input
-                            id: 'compose-subject',
-                            name: 'compose-subject',
-                            ref: 'subject',
-                            valueLink: @linkState('subject'),
-                            type: 'text',
-                            className: 'form-control compose-subject',
+                            id: 'compose-subject'
+                            name: 'compose-subject'
+                            ref: 'subject'
+                            valueLink: @linkState('subject')
+                            type: 'text'
+                            className: 'form-control compose-subject'
                             placeholder: t "compose subject help"

                 div className: '',
@@ -237,7 +237,7 @@
         if not Array.isArray(@state.to) or @state.to.length is 0
             setTimeout ->
                 document.getElementById('compose-to')?.focus()
-            , 0
+            , 10
         else if @props.inReplyTo?
             document.getElementById('compose-editor')?.focus()


Simple dumper for type check failures.

Simple dumper for type check failures.

--- client/src/scripts/dom.global.coffee
+++ client/src/scripts/dom.global.coffee
@@ -1,3 +1,27 @@
+if window
+  dumpTypecheckErrors = (arg) ->
+    tab = '  '
+    dump = (lines, offset, arg) ->
+      if isArray arg
+        indent = offset + tab
+        for item in arg
+          dump lines, indent, item
+      else if isString arg
+        lines.push offset + arg
+      return
+
+    dump lines=[], '', arg
+    lines
+
+  typecheck = (value, type) ->
+    if error = T.check value, type
+      lines = dumpTypecheckErrors error
+      message = "Typecheck failed for #{type.name}"
+      window.steam.context.fatal message, errors: lines
+      console.error message, lines
+      no
+    else
+      yes

 do ->
   setAttributes = (el, attributes) ->


create tmpdir sync.

create tmpdir sync.

--- test/spec/ng-infinite-scroll.spec.coffee
+++ test/spec/ng-infinite-scroll.spec.coffee
@@ -111,7 +111,7 @@
         describe "with #{container} as container", ->

           replaceIndexFile = (attrs, throttle) ->
-            mkdirp tmpDir
+            mkdirp.sync tmpDir
             fs.writeFileSync(pathToDocument, getTemplate(angularVersion, container, attrs, throttle))

           describe "without throttling", ->


patch to not override engine version, closes #165


remove automatic dialog open

remove automatic dialog open

--- app/components/manage-group-funds-page/manage-group-funds-page.coffee
+++ app/components/manage-group-funds-page/manage-group-funds-page.coffee
@@ -62,12 +62,10 @@
               scope: $scope
               controller: ($scope, $mdDialog) ->
                 $scope.csvUploadErrors = response.data.errors
-
+
                 $scope.cancel = ->
                   $mdDialog.cancel()
                 $scope.tryAgain = ->
                   $scope.openUploadCSVPrimerDialog()

-    $scope.openUploadCSVPrimerDialog()
-
     return


Get a handle to dispose observer

Get a handle to dispose observer

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -57,7 +57,7 @@
     @on 'mousedown', @onMouseDown
     @miniVisibleArea.on 'mousedown', @onDragStart

-    @paneView.model.observeActiveItem @onActiveItemChanged
+    @obsPane = @paneView.model.observeActiveItem @onActiveItemChanged

     # Fix items movin to another pane.
     @subscribe @paneView.model, 'item-removed', (item) -> item.off? '.minimap'
@@ -108,6 +108,7 @@

   destroy: ->
     @off()
+    @obsPane.dispose()
     @unsubscribe()
     @observer.disconnect()


Don't order columns on ENTER

Don't order columns on ENTER

--- app/assets/javascripts/effective_datatables/initialize.js.coffee
+++ app/assets/javascripts/effective_datatables/initialize.js.coffee
@@ -156,6 +156,7 @@

         $input.parent().on 'click', (event) -> false # Dont order columns when you click inside the input
         $input.parent().on 'mousedown', (event) -> event.stopPropagation() # Dont order columns when you click inside the input
+        $input.parent().on 'keypress', (event) -> event.stopPropagation() # Don't order columns when you type inside the input

         if $input.is('select')
           $input.on 'change', (event) -> dataTableSearch($(event.currentTarget))


clear the query string

clear the query string

--- app/assets/javascripts/effective_datatables/initialize.js.coffee
+++ app/assets/javascripts/effective_datatables/initialize.js.coffee
@@ -63,6 +63,6 @@
 $(document).on 'page:change', -> initializeDataTables()
 resetTable = (event) ->
   $('table[data-effective-datatables-table]').DataTable().state.clear()
-  window.location.reload()
+  window.location = window.location.pathname

 $(document).on 'click', '[data-effective-datatables-reset-filters]', resetTable


When the angularjs app loads, by default load the start page.

This doesn't add anything right now, but in the future will start timers to update the status of any jobs that are running, the status of which will be displayed in the start page.

--- app/assets/javascripts/ng-controllers/ng-FeedbunchCtrl.js.coffee
+++ app/assets/javascripts/ng-controllers/ng-FeedbunchCtrl.js.coffee
@@ -15,6 +15,9 @@
   #--------------------------------------------
   # APPLICATION INITIALIZATION
   #--------------------------------------------
+
+  # Show start page by default
+  startPageSvc.show_start_page()

   # By default sidebar is visible in smartphones and other small screens
   sidebarVisibleSvc.set true


feat(LMS-1523): fixed issue LMS-1523 - The system is not saving the sections that were assigned to a discussion when the user attach files

--- app/coffeescripts/backbone-ext/Backbone.syncWithMultipart.coffee
+++ app/coffeescripts/backbone-ext/Backbone.syncWithMultipart.coffee
@@ -22,10 +22,20 @@
     # Create a hidden form
     httpMethod = {create: 'POST', update: 'PUT', delete: 'DELETE', read: 'GET'}[method]
     toForm = (object, nested, asArray) ->
+      useModels = false;
+      if nested and object.models
+        if object.models.length > 0
+          useModels = true
+          object = object.models[0].attributes
+
       inputs = _.map object, (attr, key) ->
-
-        key = "#{nested}[#{if asArray then '' else key}]" if nested
-
+
+        if nested
+          if useModels
+            key = "#{nested}[][#{if asArray then '' else key}]"
+          else
+            key = "#{nested}[#{if asArray then '' else key}]"
+
         if _.isElement(attr)
           # leave a copy in the original form, since we're moving it
           $orig = $(attr)


Only remove tabs for items when the tab is found

Closes #96

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -106,7 +106,7 @@
     tab.updateTitle()

   removeTabForItem: (item) ->
-    @tabForItem(item).destroy()
+    @tabForItem(item)?.destroy()
     tab.updateTitle() for tab in @getTabs()
     return


one more test...

one more test...

--- test/test.coffee
+++ test/test.coffee
@@ -276,3 +276,34 @@
       r2.set 'c', 4

       z.should.eql 5
+
+    it 'events should fire for deeply nested refs', ->
+      tree =
+        a:
+          b:
+            c:
+              d: 1
+
+      r = new Ref clone tree
+
+      # ref
+      r2 = r.ref 'a.b.c'
+
+      z = 0
+      r.on 'set', (k, x, y) ->
+        if k == 'a.b.c.d'
+          if z == 0
+            x.should.eql 2
+            y.should.eql 1
+            z++
+
+      r2.on 'set', (k, x, y) ->
+        if k == 'd'
+          if z == 1
+            x.should.eql 2
+            y.should.eql 1
+            z++
+
+      r.set 'a.b.c.d', 2
+
+      z.should.eql 2


Refactored tests to allow .only to work as expected. Added new test for multiple predicates

--- test/test.coffee
+++ test/test.coffee
@@ -6,43 +6,28 @@
 require('../src/breeze-json-api-uribuilder.js')
 expect = require('chai').expect

+uriBuilder = undefined
+manager = undefined
+query = undefined
+buildUri = (query) -> uriBuilder.buildUri query, manager.metadataStore

-describe "000.Init", ->
-	it "should not be null", -> expect(breeze).to.not.be.null
+before ->
+	breeze.config.initializeAdapterInstances uriBuilder: 'json-api'
+	ds = new (breeze.DataService)(
+		serviceName: 'http://localhost:9000'
+		hasServerMetadata: false)
+	manager = new (breeze.EntityManager)(dataService: ds)
+	uriBuilder = breeze.config.getAdapterInstance('uriBuilder', 'json-api')

+beforeEach -> query = (new (breeze.EntityQuery)).from('people')
+
+describe "000. Init", ->
+	it "should not be null", -> expect(breeze).to.not.be.null

 describe "005. Basic query building", ->
-	uriBuilder = undefined
-	manager = undefined
-	query = undefined
-	buildUri = (query) -> uriBuilder.buildUri query, manager.metadataStore
-

 	describe 'breeze.EntityQuery().from("people")', ->
-		before ->
-			breeze.config.initializeAdapterInstances uriBuilder: 'json-api'
-			ds = new (breeze.DataService)(
-				serviceName: 'http://localhost:9000'
-				hasServerMetadata: false)
-			manager = new (breeze.EntityManager)(dataService: ds)
-			uriBuilder = breeze.config.getAdapterInstance('uriBuilder', 'json-api')

-		beforeEach -> query = (new (breeze.EntityQuery)).from('people')
-
-		describe 'orderBy single', ->
-			it '"firstName" returns "people?sort=firstName"', -> expect(decodeURIComponent(buildUri(query.orderBy('firstName')))).to.equal('people?sort=firstName')
-			it '"firstName desc" returns "people?sort=-firstName"', -> expect(decodeURIComponent(buildUri(query.orderBy('firstName desc')))).to.equal('people?sort=-firstName')
-			it '"firstName desc", false" returns "people?sort=firstName"', -> expect(decodeURIComponent(buildUri(query.orderBy('firstName desc', false)))).to.equal('people?sort=firstName')
-			it '"firstName, true" returns "people?sort=-firstName"', -> expect(decodeURIComponent(buildUri(query.orderBy('firstName', true)))).to.equal('people?sort=-firstName')
-
-		describe 'orderBy multiple', ->
-			it '"firstName, lastName" returns "people?sort=firstName,lastName"', -> expect(decodeURIComponent(buildUri(query.orderBy('firstName, lastName')))).to.equal('people?sort=firstName,lastName')
-			it '"firstName desc, lastName" returns "people?sort=-firstName,lastName"', -> expect(decodeURIComponent(buildUri(query.orderBy('firstName desc, lastName')))).to.equal('people?sort=-firstName,lastName')
-			it '"firstName, lastName desc" returns "people?sort=firstName,-lastName"', -> expect(decodeURIComponent(buildUri(query.orderBy('firstName, lastName desc')))).to.equal('people?sort=firstName,-lastName')
-			it '"firstName desc, lastName desc" returns "people?sort=-firstName,-lastName"', -> expect(decodeURIComponent(buildUri(query.orderBy('firstName desc, lastName desc')))).to.equal('people?sort=-firstName,-lastName')
-			it '"firstName desc, lastName desc", false returns "people?sort=firstName,lastName"', -> expect(decodeURIComponent(buildUri(query.orderBy('firstName desc, lastName desc', false)))).to.equal('people?sort=firstName,lastName')
-			it '"firstName, lastName", true returns "people?sort=-firstName,-lastName"', -> expect(decodeURIComponent(buildUri(query.orderBy('firstName, lastName', true)))).to.equal('people?sort=-firstName,-lastName')
-
 		describe 'where', ->
 			it 'unset returns "people"', -> expect(buildUri(query)).to.equal('people')
 			it '"name == Scott" returns "people?filter[name]=Scott"', -> expect(decodeURIComponent(buildUri(query.where("name", "==", "Scott")))).to.equal('people?filter[name]=Scott')
@@ -59,12 +44,34 @@
 			it '"age lt 20" to throw unsupported operator exception', -> expect( -> buildUri(query.where('age','lt', 20))).to.throw('lt is currently not supported by JSON-API')
 			it '"age le 20" to throw unsupported operator exception', -> expect( -> buildUri(query.where('age','le', 20))).to.throw('le is currently not supported by JSON-API')

+describe "010. Advanced query building", ->
+
 		describe 'Predicates', ->

-			andPredicate = breeze.Predicate.create('firstName', '==', 'Scott').and(   'lastName', '==', 'Raynor');
-			it "'firstName', '==', 'Scott' and 'lastName', '==', 'Raynor' returns people?filter[firstName]=Scott&filter[lastName]=Raynor", -> expect(decodeURIComponent(buildUri(query.where(andPredicate)))).to.equal('people?filter[firstName]=Scott&filter[lastName]=Raynor');
+				andPredicate = breeze.Predicate.create('firstName', '==', 'Scott').and(   'lastName', '==', 'Raynor');
+				it "'firstName', '==', 'Scott' AND 'lastName', '==', 'Raynor' returns people?filter[firstName]=Scott&filter[lastName]=Raynor", -> expect(decodeURIComponent(buildUri(query.where(andPredicate)))).to.equal('people?filter[firstName]=Scott&filter[lastName]=Raynor');

-			orPredicate = breeze.Predicate.create('firstName', '==', 'Scott').or(   'lastName', '==', 'Raynor');
-			it "'firstName', '==', 'Scott' OR 'lastName', '==', 'Raynor' to throw unsupported operator exception", -> expect( -> buildUri(query.where(orPredicate))).to.throw('or is currently not supported by JSON-API')
+				orPredicate = breeze.Predicate.create('firstName', '==', 'Scott').or(   'lastName', '==', 'Raynor');
+				it "'firstName', '==', 'Scott' OR 'lastName', '==', 'Raynor' to throw unsupported operator exception", -> expect( -> buildUri(query.where(orPredicate))).to.throw('or is currently not supported by JSON-API')
+
+				andOrPredicate = breeze.Predicate.create('firstName', '==', 'Scott').and(   'lastName', '==', 'Raynor').or('age', '==', '25');
+				it "'firstName', '==', 'Scott' AND 'lastName', '==', 'Raynor' or 'age' == '25' to throw unsupported operator exception", -> expect( -> buildUri(query.where(orPredicate))).to.throw('or is currently not supported by JSON-API')

+
+describe '015. OrderBy', ->
+
+		describe 'orderBy single', ->
+				it '"firstName" returns "people?sort=firstName"', -> expect(decodeURIComponent(buildUri(query.orderBy('firstName')))).to.equal('people?sort=firstName')
+				it '"firstName desc" returns "people?sort=-firstName"', -> expect(decodeURIComponent(buildUri(query.orderBy('firstName desc')))).to.equal('people?sort=-firstName')
+				it '"firstName desc", false" returns "people?sort=firstName"', -> expect(decodeURIComponent(buildUri(query.orderBy('firstName desc', false)))).to.equal('people?sort=firstName')
+				it '"firstName, true" returns "people?sort=-firstName"', -> expect(decodeURIComponent(buildUri(query.orderBy('firstName', true)))).to.equal('people?sort=-firstName')
+
+		describe 'orderBy multiple', ->
+				it '"firstName, lastName" returns "people?sort=firstName,lastName"', -> expect(decodeURIComponent(buildUri(query.orderBy('firstName, lastName')))).to.equal('people?sort=firstName,lastName')
+				it '"firstName desc, lastName" returns "people?sort=-firstName,lastName"', -> expect(decodeURIComponent(buildUri(query.orderBy('firstName desc, lastName')))).to.equal('people?sort=-firstName,lastName')
+				it '"firstName, lastName desc" returns "people?sort=firstName,-lastName"', -> expect(decodeURIComponent(buildUri(query.orderBy('firstName, lastName desc')))).to.equal('people?sort=firstName,-lastName')
+				it '"firstName desc, lastName desc" returns "people?sort=-firstName,-lastName"', -> expect(decodeURIComponent(buildUri(query.orderBy('firstName desc, lastName desc')))).to.equal('people?sort=-firstName,-lastName')
+				it '"firstName desc, lastName desc", false returns "people?sort=firstName,lastName"', -> expect(decodeURIComponent(buildUri(query.orderBy('firstName desc, lastName desc', false)))).to.equal('people?sort=firstName,lastName')
+				it '"firstName, lastName", true returns "people?sort=-firstName,-lastName"', -> expect(decodeURIComponent(buildUri(query.orderBy('firstName, lastName', true)))).to.equal('people?sort=-firstName,-lastName')
+


Added tests for ,

Added tests for ,

--- test/test.coffee
+++ test/test.coffee
@@ -250,6 +250,32 @@
     assert.equal result.length, 1
     assert.equal result[0].title, "About"

+
+  it "$not operator", ->
+    a = create()
+    result = _.query a, {$not: {likes:  {$lt: 12}}}
+    assert.equal result.length, 2
+
+  #  These tests fail, but would pass if it $not worked parallel to MongoDB
+  #  it "$not operator", ->
+  #    a = create()
+  #    result = _.query a, {likes:  {$not: {$lt: 12}}}
+  #    assert.equal result.length, 2
+  #
+  #  it "$not operator", ->
+  #    a = create()
+  #    result = _.query a, likes: {$not:  12}
+  #    assert.equal result.length, 2
+  #
+  #  it "$not $equal operator", ->
+  #    a = create()
+  #    result = _.query a, likes: {$not:  {$equal: 12}}
+  #    assert.equal result.length, 2
+
+  #  it "$not $equal operator", ->
+  #    a = create()
+  #    result = _.query a, likes: {$not:  {ne: 12}}
+  #    assert.equal result.length, 1

@@ -705,4 +731,36 @@
   it "$not operator - mongo style", ->
     a = create()
     result = _.query a, {likes:  {$not: {$lt: 12}}}
-    assert.equal result.length, 2+    assert.equal result.length, 2
+
+  # This is parallel to MongoDB
+  it "$not operator - mongo style", ->
+    a = create()
+    result = _.query a, {likes:  {$not: 12}}
+    assert.equal result.length, 2
+
+  it "combination of $gt and $lt - mongo style", ->
+    a = create()
+    result = _.query a, {likes: { $gt: 2, $lt: 20}}
+    assert.equal result.length, 1
+
+  it "$not combination of $gt and $lt  - mongo style", ->
+    a = create()
+    result = _.query a, {likes: {$not: { $gt: 2, $lt: 20}}}
+    assert.equal result.length, 2
+
+  it "$nor combination of $gt and $lt  - expressions ", ->
+    a = create()
+    result = _.query a, {$nor: [{likes: { $gt: 2}}, {likes: { $lt: 20}}]}
+    assert.equal result.length, 0
+
+#  This query is not a valid MongoDB query, but if it were one would expect it to yield an empty set
+#  it "$nor combination of $gt and $lt  - values", ->
+#    a = create()
+#    result = _.query a, {likes: {$nor: [{ $gt: 2}, {$lt: 20}]}}
+#    assert.equal result.length, 0
+
+  it "combination of $gt  and $not", ->
+    a = create()
+    result = _.query a, {likes: { $not: 2, $lt: 20}}
+    assert.equal result.length, 1


#1486 fix pagination for marionette v3

#1486 fix pagination for marionette v3

--- app/assets/javascripts/admin/views/pagination_view.coffee
+++ app/assets/javascripts/admin/views/pagination_view.coffee
@@ -4,7 +4,7 @@

 class PaginationView extends Marionette.View

-  pagingatorTemplate : _.template("""
+  paginatorTemplate : _.template("""
     <li class="first <% if (Pagination.currentPage == 0) { %> disabled <% } %>">

@@ -52,6 +52,7 @@
   """)

   className : "container wide"
+
   templateContext : ->
     paginationInfo = @collection.getPaginationInfo()
     pageRange : _.range(
@@ -74,38 +75,46 @@

   initialize : (options) ->
+
     @options = options
     @listenToOnce(@collection, "reset", @searchByHash)
     @listenTo(@collection, "reset", @render)

   goFirst : (evt) ->
+
     evt?.preventDefault()
     @collection.getFirstPage()

   goLast : (evt) ->
+
     evt?.preventDefault()
     @collection.getLastPage()

   goBack : (evt) ->
+
     evt?.preventDefault()
     @collection.getPreviousPage()

   goNext : (evt) ->
+
     evt?.preventDefault()
     @collection.getNextPage()

   handleClickPage : (evt) ->
+
     evt?.preventDefault()
     page = $(evt.target).text()
     @collection.getPage(parseInt(page) - 1)

   goToPage : (page) ->
+
     @collection.getPage(page)

   addElement : ->
+
     app.vent.trigger("paginationView:addElement")

@@ -118,23 +127,19 @@

   render : ->
+
     this._ensureViewIsIntact()
     this.triggerMethod('before:render', this)

     obj = @templateContext()
-    if not this.isRendered
+    if not this._isRendered
       @$el.html(@template(obj))
-      this.isRendered = true
+    this._isRendered = true

-    @$el.find("ul.pagination").html(@pagingatorTemplate(obj))
+    @$el.find("ul.pagination").html(@paginatorTemplate(obj))
     this.bindUIElements()
     this.triggerMethod('render', this)
     return this
-
-
-  # afterRender : ->
-
-  #   @ui.inputSearch.val(@collection.state.filterQuery)

   searchByHash : ->


Make conditions easier to read

Make conditions easier to read

Combining both the inline and "traditional" `if` styles made the code
difficult to read, particularly when compiled to JavaScript.

--- app/assets/javascripts/budget_edit_associations.js.coffee
+++ app/assets/javascripts/budget_edit_associations.js.coffee
@@ -20,5 +20,5 @@
       click: ->
         div_id = $(this).data().toggle
         $(".js-budget-users-list").each ->
-          if this.id != div_id
-            $(this).addClass("is-hidden") if !$(this).hasClass("is-hidden")
+          if this.id != div_id && !$(this).hasClass("is-hidden")
+            $(this).addClass("is-hidden")


fixed snippet dropping bug caused by typo in last commit

--- app/assets/javascripts/mercury/regions/snippets.js.coffee
+++ app/assets/javascripts/mercury/regions/snippets.js.coffee
@@ -87,8 +87,9 @@
     if (snippet)
       return @element.data('accepted_snippets') == '*' ||
              @element.data('accepted_snippets').indexOf(snippet.name) != -1 &&
-             @element.find('[data-snippet]').length < @element.data('number_of_snippets') &&
-             @element.data('')
+             @element.find('[data-snippet]').length < @element.data('number_of_snippets') #&&
+             # TODO: find a way to tell if the snippet is already on the page or not
+             #@element.data('[data-snippet_on_page') != 'true'
      else
       return false


yet another bug fix

yet another bug fix

--- app/assets/javascripts/mixins/order_items_mixin.js.coffee
+++ app/assets/javascripts/mixins/order_items_mixin.js.coffee
@@ -60,7 +60,6 @@

   ice: (->
     @get("orderItems.content").reduce (prev, orderItem) =>
-      ice = orderItem.get "item.ice#{@get("modelName")}"
-      prev + orderItem.get("cantidad") * ice
+      prev + orderItem.get("ice#{@get("modelName")}")
     , 0
   ).property("orderItems.@each.item.baseCompra", "orderItems.@each.item.baseVenta", "orderItems.@each.cantidad")


Fix preloading

Fix preloading

--- app/assets/javascripts/views/event/artists_view.js.coffee
+++ app/assets/javascripts/views/event/artists_view.js.coffee
@@ -17,7 +17,8 @@

     view.preload() for view in views[0..2]

-    @set('nextSlideView', views[3])
+    @set('nextSlideView', views[2])
+    @preloadNextSlideView()

     setTimeout (=> @initilizeSlider()), 100

@@ -39,7 +40,4 @@
     return if index >= views.length

     @set('nextSlideView', views[index])
-
-  nextSlideViewDidChange: (->
     @get('nextSlideView').preload()
-  ).observes('nextSlideView')


fix users admin merge autocomplete box

fix users admin merge autocomplete box

test plan:
* as an account admin, visit /users/:id/admin_merge
* the "Find a User" autocomplete box should work
 (note: the minimum search length is 4 characters)

fixes #CNVS-11476

Change-Id: I74095750a23085d63b814d80e40dacc62133ff7c
Reviewed-on: https://gerrit.instructure.com/34224
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Jeremy Stanley <b3f594e10a9edcf5413cf1190121d45078c62290@instructure.com>
Product-Review: Jeremy Stanley <b3f594e10a9edcf5413cf1190121d45078c62290@instructure.com>
QA-Review: Clare Strong <40d6bb3ed7fef516c73274131d5a837c16de74fd@instructure.com>

--- app/coffeescripts/bundles/legacy/users_admin_merge.coffee
+++ app/coffeescripts/bundles/legacy/users_admin_merge.coffee
@@ -1,4 +1,7 @@
-jQuery ->
+require [
+  "jquery",
+  'compiled/behaviors/autocomplete'
+], ($) ->
   $select_name = $("#select_name")
   $selected_name = $("#selected_name")
   $("#account_select").change(->
@@ -10,7 +13,6 @@
     $input.autocomplete
       minLength: 4
       source: $input.data("autocompleteSource")
-
     $input.bind "autocompleteselect autocompletechange", (event, ui) ->
       if ui.item
         $selected_name.text ui.item.label


only work with classes from the student term in the first place!

--- apis/v0p1.coffee
+++ apis/v0p1.coffee
@@ -123,6 +123,7 @@
 			courses: []
 			tid: term._id #FIXME!
 		}
+		classes = _.select(classes, {term: {code: term.code}})
 		for c in classes
 			opts.courses.push _.select(etabits.data.programsByCode[c.course.program].courses, {code: c.course.code})[0]._id
 		#console.log(opts)
@@ -136,7 +137,7 @@
 					hour: time.hour
 					day: time.day
 				}
-			classes = _.chain(classes).select({term: {code: term.code}}).sort(sortTimedClass).value()
+			classes = _.chain(classes).sort(sortTimedClass).value()
 			res.json({success: true, data: classes})


EmptyShell: icon -> link

EmptyShell: icon -> link

--- coffee/src/shells/empty.shell.coffee
+++ coffee/src/shells/empty.shell.coffee
@@ -13,7 +13,7 @@
   id: 'acorn.EmptyShell'
   title: 'EmptyShell'
   description: 'an empty, nutless shell'
-  icon: 'icon-check-empty'
+  icon: 'icon-link'


Client: Activity: Added onKeyDown event handler to check which key pressed and add handler for this key

--- client/activity/lib/components/channelparticipantavatars/index.coffee
+++ client/activity/lib/components/channelparticipantavatars/index.coffee
@@ -1,12 +1,28 @@
-kd                   = require 'kd'
-React                = require 'kd-react'
-Avatar               = require 'app/components/profile/avatar'
-immutable            = require 'immutable'
-classnames           = require 'classnames'
-ProfileLinkContainer = require 'app/components/profile/profilelinkcontainer'
-
+kd                          = require 'kd'
+whoami                      = require 'app/util/whoami'
+React                       = require 'kd-react'
+Avatar                      = require 'app/components/profile/avatar'
+KDReactorMixin              = require 'app/flux/reactormixin'
+immutable                   = require 'immutable'
+classnames                  = require 'classnames'
+AppFlux                     = require 'app/flux'
+ActivityFlux                = require 'activity/flux'
+ProfileLinkContainer        = require 'app/components/profile/profilelinkcontainer'
+ChannelParticipantsDropdown = require 'activity/components/channelparticipantsdropdown'

 module.exports = class ChannelParticipantAvatars extends React.Component
+
+  TAB               = 9
+  ESC               = 27
+  ENTER             = 13
+  UP_ARROW          = 38
+  DOWN_ARROW        = 40
+  PREVIEW_COUNT     = 0
+  MAX_PREVIEW_COUNT = 4
+
+  @defaultProps =
+    channelThread : null
+    participants  : null

   constructor: (props) ->

@@ -184,6 +200,16 @@

     user.setChannelParticipantsInputQuery query
     channel.setChannelParticipantsDropdownVisibility yes
+  onKeyDown: (event) ->
+
+    switch event.which
+      when ENTER       then @onEnter event
+      when ESC         then @onEsc event
+      when TAB         then @onNextPosition event, { isTab : yes }
+      when DOWN_ARROW  then @onNextPosition event, { isDownArrow : yes }
+      when UP_ARROW    then @onPrevPosition event, { isUpArrow : yes }
+
+
   renderAddNewParticipantInput: ->

     <div className={@getNewParticipantInputClassNames()}>


Change how the express debug is run to get rid of an error msg

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -31,8 +31,8 @@
     express:
       # Common options - Start our app with coffee
       options:
-        cmd:  'coffee'
         port: env.port
+        opts: [ './node_modules/.bin/coffee' ]

       # Dev options - Set dev env and debug vars
       dev:


Gruntfile: Use Grunt's builtin for writing files.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -114,10 +114,8 @@

   grunt.registerTask 'compile:json', 'Compile all data JSON into one file', ->
     fs = require('fs')
-    done = this.async()
     {GenerationJSON} = require './server/generations'
     EventPokemon = require './shared/event_pokemon.json'
     contents = """var Generations = #{JSON.stringify(GenerationJSON)};
     var EventPokemon = #{JSON.stringify(EventPokemon)}"""
-    fs.mkdir "./public/js", ->
-      fs.writeFile("./public/js/data.js", contents, done)
+    grunt.file.write('./public/js/data.js', contents)


Updating grunt for single file tests (`grunt test:test`)

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -24,14 +24,43 @@
         tasks: [ 'coffee' ]

     mochaTest:
-      test:
+      xunit:
+        options:
+          reporter: 'xunit'
+          captureFile: 'spec.xml'
+          require: [
+            'coffee-script/register'
+          ]
+        src: ['spec/**/*.coffee']
+      all:
         options:
           reporter: 'spec'
           require: [
-            'coffee-script'
+            'coffee-script/register'
           ]
-        src: [ 'spec/**/*.coffee' ]
+        src: ['spec/**/*.coffee']
+      one:
+        options:
+          require: [
+            'coffee-script/register'
+          ]
+        src: [ ]

-  grunt.registerTask 'test', [ 'mochaTest' ]
+  grunt.registerTask 'xunit', [ 'mochaTest:xunit' ]
+  grunt.registerTask 'test', (name) ->
+    switch name
+      when undefined, 'all'
+        grunt.task.run 'mochaTest:all'
+      else
+        if name.indexOf('/') isnt -1
+          [ dirname, basename ] = name.split '/'
+          path = "spec/#{dirname}/spec-#{basename}.coffee"
+        else
+          path = "spec/spec-#{name}.coffee"
+
+        grunt.log.writeln "Testing #{path}"
+        grunt.config.set 'mochaTest.one.src', [ path ]
+        grunt.task.run [ 'mochaTest:one' ]
+
   grunt.registerTask 'compile', [ 'clean:lib', 'coffee' ]
   grunt.registerTask 'default', [ 'compile' ]


updated build script

updated build script

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -88,9 +88,8 @@
       'requirejs'
       'copy'
       'express'
-      'exec'
+      'exec:crawl'
       'replace'
-      'docco'
     ]

     config =


Also push to github on release

Also push to github on release

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -147,6 +147,15 @@
       console.log("Error running git tag: " + error) if error?
       done(!error?)

+  grunt.registerTask "git-push", "Push a release to github", ->
+    exec = require("child_process").exec
+    done = this.async()
+    releaseVersion = grunt.template.process("<%= pkg.version %>")
+
+    child = exec "git push origin master \"v#{releaseVersion}\"", (error, stdout, stderr) ->
+      console.log("Error running git push: " + error) if error?
+      done(!error?)
+
   grunt.registerTask "stats", ["uglify", "uglify-stats"]

   grunt.registerTask "uglify", "Uglifies bugsnag.js", () ->
@@ -169,7 +178,7 @@
             done(!error?)

   # Release meta-task
-  grunt.registerTask "release", ["jshint", "uglify", "docco", "git-tag", "s3", "invalidate_cloudfront"]
+  grunt.registerTask "release", ["jshint", "uglify", "docco", "git-tag", "s3", "invalidate_cloudfront", "git-push"]

   # Run a webserver for testing
   grunt.registerTask "server", ["connect:server:keepalive"]


build tasks fixed

build tasks fixed

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -125,20 +125,21 @@
     # generate modules.json with directories inside src

     # read generated modules list
-    modules = grunt.file.readJSON 'modules.json'
+    modules     = grunt.file.readJSON 'modules.json'
     concatFiles = grunt.config.get 'concat.js.files'
     uglifyFiles = grunt.config.get 'uglify.dist.files'

-    modules.forEach module ->
-      concatFiles['<%= appConfig.dist %>/' + module.location + '/' + module.location + '.js'] =  ['<%= appConfig.app %>/' + module.location + '/{,scripts/}{,*/}*.js']
-      uglifyFiles['<%= appConfig.dist %>/' + module.location + '/' + module.location + '.min.js'] = ['.tmp/' + module.location +'/**.js']
+    for module in modules
+      do (module) ->
+        concatFiles["<%= appConfig.dist %>/#{ module.location }/#{ module.location }.js"]     =  ["<%= appConfig.app %>/#{ module.location }/{,scripts/}{,*/}*.js"]
+        uglifyFiles["<%= appConfig.dist %>/#{ module.location }/#{ module.location }.min.js"] = [".tmp/#{ module.location }/**.js"]

-    grunt.config.set 'concat.js.files', concatFiles
-    grunt.config.set 'uglify.dist.files', uglifyFiles
+        grunt.config.set 'concat.js.files', concatFiles
+        grunt.config.set 'uglify.dist.files', uglifyFiles

   # Scan directories and explore submodules
   grunt.registerTask 'prepareBuild', [
-    'folder_list',
+    'folder_list'
     'setConfigsForSubmodules'
   ]


Add test task

Add test task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -6,7 +6,8 @@

 BUILD_DIR = '/tmp/atom-build/atom-shell'
 APP_NAME = 'Atom.app'
-APP_DIR = path.join(BUILD_DIR, APP_NAME, 'Contents', 'Resources', 'app')
+CONTENTS_DIR = path.join(BUILD_DIR, APP_NAME, 'Contents')
+APP_DIR = path.join(CONTENTS_DIR, 'Resources', 'app')
 INSTALL_DIR = path.join('/Applications', APP_NAME)

 module.exports = (grunt) ->
@@ -221,6 +222,15 @@
       exec('script/update-atom-shell', callback)
     grunt.util.async.waterfall commands, (error) -> done(!error?)

+  grunt.registerTask 'test', 'Run the specs', ->
+    done = @async()
+    commands = []
+    commands.push (callback) ->
+      exec('pkill', ['Atom'], callback)
+    commands.push (result, callback) ->
+      exec(path.join(CONTENTS_DIR, 'MacOS', 'Atom'), ['--test', '--resource-path', __dirname], callback)
+    grunt.util.async.waterfall commands, (error) -> done(!error?)
+
   grunt.registerTask('compile', ['coffee', 'less', 'cson'])
   grunt.registerTask('lint', ['coffeelint', 'csslint'])
   grunt.registerTask('default', ['lint', 'build'])


Remove dead code from Gruntfile

Remove dead code from Gruntfile

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,5 +1,3 @@
-extraRequires = ["react", "lodash"]
-
 module.exports = (grunt) ->
   grunt.initConfig
     clean: ["dist"]


added npm deployment flag

added npm deployment flag

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -55,6 +55,7 @@
     # Deployment
     bumper:
       options:
+        npm: true
         tasks: ["default"]
         files: ["package.json"]
         updateConfigs: ["pkg"]


Do not check empty string for NOFLO_THEME env variable

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -118,7 +118,7 @@
             replacement: process.env.NOFLO_APP_VERSION or '<%= pkg.version %>'
           ,
             pattern: /\$NOFLO_THEME/ig
-            replacement: process.env.NOFLO_THEME is '' or 'noflo'
+            replacement: process.env.NOFLO_THEME or 'noflo'
           ]
       analytics:
         files:


Found a config that works... let's say it will do for now...

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -67,8 +67,13 @@
   ]

   grunt.registerTask 'travis', [
-    'test',
-    'compile',
+    'build',
+    'browserify',
+    'uglify'
+    'fixtures2js'
+    'test:unit',
+    'test:integration',
+    'test:acceptance',
     'connect',
     'saucelabs-mocha'
   ]


make drone output more expressive

make drone output more expressive

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -104,12 +104,6 @@
         tasks: ['coffeeLint:tests', 'karma:singleUnit']

     mochaTest:
-      ci:
-        src: ['test/server/**/*Spec.coffee']
-        options:
-          timeout: 3000
-          ignoreLeaks: false
-          reporter: 'dot'
       unit:
         src: ['test/server/**/*Spec.coffee']
         options:
@@ -147,7 +141,7 @@
   , ['build', 'mochaTest:unit', 'karma:unit', 'clean:bin']

   grunt.registerTask 'ci'
-  , ['build', 'mochaTest:ci', 'karma:unit' ]
+  , ['default']

   grunt.registerTask 'coverage'
   , ['build', 'karma:coverage', 'clean:bin']


Fix reading secrets.json

Fix reading secrets.json

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,5 +1,8 @@
 module.exports = (grunt) ->
-  secrets = grunt.file.readJSON("secrets.json")
+  secrets = try
+    grunt.file.readJSON("secrets.json")
+  catch e
+    {}

   # Configuration
   grunt.initConfig


Activate mangle option of uglifyjs for generate more optimized production js files.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -43,7 +43,7 @@
         uglify: {
             options: {
                 banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n',
-                mangle: false,
+                mangle: true,
                 report: 'min'
             },


Add all script in js/ for portal compatibility

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -24,11 +24,14 @@

   config.watch.coffee.tasks.push 'copy:js', 'concat'

+  config.clean.deploy = 'build/<%= relativePath %>/script/'
+
+  # Copy all to 'js' for Portal URLs compatibility, e.g. "http://io.vtex.com.br/portal-plugins/2.7.3/js/portal-template-as-modal.min.js"
   config.copy.js =
     files: [
       expand: true
       cwd: 'build/<%= relativePath %>/script/'
-      src: ['expiration/**/*.js']
+      src: ['**/*.js']
       dest: "build/<%= relativePath %>/js/"
     ]

@@ -61,7 +64,7 @@
   # Building block tasks
     build: ['clean', 'copy:main', 'copy:pkg', 'coffee', 'copy:js','dust', 'concat']
   # Deploy tasks
-    dist: ['build', 'uglify:main', 'copy:deploy'] # Dist - minifies files
+    dist: ['build', 'uglify:main', 'clean:deploy', 'copy:deploy'] # Dist - minifies files
     test: []
     vtex_deploy: ['shell:cp']
   # Development tasks


Remove post-help line from grunt release task

The changelog module was removed so this is no longer applicable.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -477,8 +477,7 @@
         run "rm -rf #{ pkg.name }"

     grunt.registerTask 'release-help', 'Prints the post-release steps', ->
-        grunt.log.ok 'Did you remember to update the cilantro/changelog module?'
-        grunt.log.ok 'Push the code and tags: git push origin develop && git push --tags'
+        grunt.log.ok 'Push the code and tags: git push && git push --tags'
         grunt.log.ok "Go to #{ pkg.homepage }/releases to update the release descriptions and upload the binaries"

     grunt.registerTask 'release', 'Builds the distribution files, creates the release binaries, and creates a Git tag', [


Fix up some Gruntfile errors

Fix up some Gruntfile errors

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,6 +1,7 @@
 fs = require "fs"
 spawn = require("child_process").spawn
 rimraf = require "rimraf"
+Path = require "path"

 SERVICES = [{
 	name: "web"
@@ -63,7 +64,7 @@
 			grunt.registerTask "update:#{service.name}", "Checkout and update the #{service.name} service", () ->
 				done = @async()
 				Helpers.updateService(service.name, done)
-			grunt.registerTask "run:#{service.name}", "Run the ShareLaTeX #{service.name} service", ["bunyan", "execute:web"]
+			grunt.registerTask "run:#{service.name}", "Run the ShareLaTeX #{service.name} service", ["bunyan", "execute:#{service.name}"]

 	grunt.registerTask 'install:all', "Download and set up all ShareLaTeX services", ("install:#{service.name}" for service in SERVICES)
 	grunt.registerTask 'install', 'install:all'
@@ -132,5 +133,5 @@
 				return callback(error) if error?
 				for file in fs.readdirSync("config-local")
 					unless file == ".git"
-						fs.symlinkSync("config-local/#{file}", "config/#{file}")
+						fs.symlinkSync(Path.resolve("config-local/#{file}"), "config/#{file}")
 				callback()


client: use new prop structure in credit card form

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/component-lab/CreateCreditCardForm/CreateCreditCardForm.coffee
+++ client/component-lab/CreateCreditCardForm/CreateCreditCardForm.coffee
@@ -10,13 +10,15 @@

 module.exports = CreateCreditCardForm = (props) ->

-  { formValues, initialValues, handleSubmit, isDirty, loading } = props
+  { isDirty, handleSubmit, formValues: values, placeholders, loading } = props

-  values = formValues or initialValues

-  { brand } = values
-
-  brand or= 'visa'
+  if isDirty
+    brand = values.brand
+    cardProps = values
+  else
+    brand = placeholders.brand
+    cardProps = placeholders

   <form className={styles.main} onSubmit={handleSubmit}>
     <Row bottom='xs' between='xs'>
@@ -27,6 +29,7 @@
               disabled={loading}
               fieldType='number'
               name='number'
+              placeholder={if isDirty then null else placeholders.number}
               brand={brand}
               title='Credit Card Number' />
           </Col>
@@ -38,7 +41,7 @@
               guide={off}
               name='exp_month'
               title='Expiration'
-              placeholder='MM' />
+              placeholder={if isDirty then 'MM' else placeholders.exp_month} />
           </Col>
           <Col xs={4} className={styles.tight}>
             <Input.Field
@@ -46,7 +49,7 @@
               guide={off}
               disabled={loading}
               name='exp_year'
-              placeholder='YYYY' />
+              placeholder={if isDirty then 'YYYY' else placeholders.exp_year} />
           </Col>
           <Col xs={4} className={styles.tight}>
             <CreditCardInput.Field
@@ -62,7 +65,7 @@
       <Col xs className={styles.mainCol}>
         <Row bottom='xs'>
           <Col xs={12}>
-            <CreditCard {...values} brand={brand} />
+            <CreditCard {...cardProps} />
           </Col>
         </Row>
       </Col>


home/stacks: use filtered data from storage on disabled users

--- client/home/lib/stacks/components/disableduserstackslist/index.coffee
+++ client/home/lib/stacks/components/disableduserstackslist/index.coffee
@@ -1,2 +1,20 @@
+kd = require 'kd'
+
+connectCompute = require 'app/providers/connectcompute'
+connectSidebar = require 'app/sidebar/connectsidebar'
+
+calculateOwnedResources = require 'app/util/calculateOwnedResources'
+
+Container = require './container'
+
 module.exports = require './view'
-module.exports.Container = require './container'
+
+computeConnector = connectCompute({
+  storage: ['stacks', 'templates', 'machines']
+  transformState: ({ stacks, templates }) -> {
+    resources: calculateOwnedResources({ stacks, templates })
+      .filter (res) -> res.stack?.getOldOwner()
+  }
+})
+
+module.exports.Container = computeConnector Container


social: another embedbox change

social: another embedbox change

--- client/activity/lib/views/comments/commentinputeditwidget.coffee
+++ client/activity/lib/views/comments/commentinputeditwidget.coffee
@@ -1,3 +1,4 @@
+_  = require 'lodash'
 kd = require 'kd'
 KDCustomHTMLView = kd.CustomHTMLView
 CommentInputWidget = require './commentinputwidget'
@@ -40,8 +41,10 @@

     @emit 'Submit'

-    {id} = data = @getData()
-    payload = @getPayload()
+    {id, payload} = data = @getData()
+    embedBoxPayload = @getEmbedBoxPayload()
+
+    payload = _.assign {}, payload, embedBoxPayload

     { appManager } = kd.singletons


more generous with permissible names and numbers

--- client/plugins/method/method.coffee
+++ client/plugins/method/method.coffee
@@ -75,11 +75,11 @@
             color = '#edd'

         try
-          if args = line.match /^(-?[0-9.]+) ([\w \/%()-]+)$/
+          if args = line.match /^([0-9.eE-]+) ([\w \/%(),-]+)$/
             result = hours = +args[1]
             line = args[2]
             output[line] = value = result
-          else if args = line.match /^([A-Z]+) ([\w \/%()-]+)$/
+          else if args = line.match /^([A-Z]+) ([\w \/%(),-]+)$/
             [value, list] = [apply(args[1], list), []]
             line = args[2]
             if output[line]? or input[line]?
@@ -88,10 +88,10 @@
             output[line] = value
           else if args = line.match /^([A-Z]+)$/
             [value, list] = [apply(args[1], list), []]
-          else if line.match /^[0-9\.-]+$/
+          else if line.match /^[0-9\.eE-]+$/
             value = +line
             line = ''
-          else if line.match /^([\w \/%()-]+)$/
+          else if line.match /^([\w \/%(),-]+)$/
             if output[line]?
               value = output[line]
             else if input[line]?


Fix indentation

Fix indentation

--- plugins/caremail/index.coffee
+++ plugins/caremail/index.coffee
@@ -1,4 +1,4 @@
- c = require "irc-colors"
+c = require "irc-colors"
 Datastore = require "nedb"
 moment = require "moment"
 dehumanize = require "dehumanize-date"


fix migration

fix migration

--- migrations/2016-12-01-team.coffee
+++ migrations/2016-12-01-team.coffee
@@ -34,7 +34,10 @@
     if member.role in _.keys roles
       member.role = roles member.role
     else
-      member.role = roles['Developer']
+      if member in oldTeam.dev
+        member.role = roles['Developer']
+      else
+        member.role = roles['Scrum Master']
     member
   project.set 'teamNew', newTeam
   project.save()


KDFramework: KDLabelView::setTitle sets default title as an empty string by default

--- components/inputs/KDLabelView.coffee
+++ components/inputs/KDLabelView.coffee
@@ -1,16 +1,16 @@
 class KDLabelView extends KDView
-	constructor:(options)->
-		@setTitle(options.title)				if options?.title?
-		super options
+  constructor:(options)->
+    @setTitle(options.title)        if options?.title?
+    super options

-	setDomElement:(cssClass)->
-		@domElement = $ "#{@getTitle()}"
+  setDomElement:(cssClass)->
+    @domElement = $ "#{@getTitle()}"

-	setTitle:(title)->
-		@labelTitle = title
+  setTitle:(title)->
+    @labelTitle = title or ''

-	updateTitle: (title) ->
-	  @setTitle title
-	  @$().html title
+  updateTitle: (title) ->
+    @setTitle title
+    @$().html title

-	getTitle:-> @labelTitle
+  getTitle:-> @labelTitle


add test for imported extension (#13)

add test for imported extension (#13)

* add tests for usage of extensions

* add tests for usage of extensions

* add tests for usage of extensions

* add tests for usage of extensions

* add test for imported extension

--- test/extensions/extension.coffee
+++ test/extensions/extension.coffee
@@ -33,3 +33,32 @@
     y = yang.parse schema
     y.should.have.property('tag').and.equal('foo')

+describe 'imported extension', ->
+  imported_schema = """
+  module foo {
+    extension c-define {
+      description
+        "Takes as argument a name string.
+        Makes the code generator use the given name in the
+        #define.";
+      argument "name";
+    }
+  }
+  """
+  schema= """
+  module bar {
+    import foo {
+      prefix foo;
+    }
+    container interfaces {
+      c-define "MY_INTERFACES";
+    }
+  }
+  """
+  it "should parse imported extension", ->
+    y1 = yang.parse imported_schema
+    yang.Registry.extends y1
+
+    y2 = yang.parse schema
+    y2.prefix.should.have.property('tag').and.equal('bar')
+


Pistachio: on embedChild replace @ with this for JS compatibility

--- lib/core/pistachio.coffee
+++ lib/core/pistachio.coffee
@@ -221,7 +221,7 @@
             "data('#{dataPath}')"
           .replace /^(?:> ?|embedChild )(.+)/, (_, subViewName) ->
             subViewNames.push subViewName.replace /\@\.?|this\./, ''
-            "embedChild(#{subViewName})"
+            "embedChild(#{subViewName.replace /@/, 'this.'})"

         @registerDataPaths dataPaths
         @registerSubViewNames subViewNames


make flush to track-changes failsafe

make flush to track-changes failsafe

--- app/coffee/HistoryManager.coffee
+++ app/coffee/HistoryManager.coffee
@@ -13,8 +13,9 @@
 			return
 		RedisManager.getHistoryType doc_id, (err, projectHistoryType) ->
 			if err?
-				logger.error {err, doc_id}, "error getting history type"
-			else if projectHistoryType is "project-history"
+				logger.warn {err, doc_id}, "error getting history type"
+				# if there's an error continue and flush to track-changes for safety
+			if projectHistoryType is "project-history"
 				logger.debug {doc_id, projectHistoryType}, "skipping track-changes flush"
 			else
 				url = "#{Settings.apis.trackchanges.url}/project/#{project_id}/doc/#{doc_id}/flush"


Fix video loading in Firefox Closes #2135

--- app/components/frame-viewer.cjsx
+++ app/components/frame-viewer.cjsx
@@ -58,7 +58,7 @@

       when 'video'

-          <video ref="videoPlayer" src={src} type={"#{type}/#{format}"} onCanPlayThrough={@handleLoad} onEnded={@endVideo} onTimeUpdate={@updateScrubber}>
+          <video ref="videoPlayer" src={src} type={"#{type}/#{format}"} preload="auto" onCanPlay={@handleLoad} onEnded={@endVideo} onTimeUpdate={@updateScrubber}>
             Your browser does not support the video format. Please upgrade your browser.
           </video>


resize input element on backspace keyup event

--- components/inputs/KDInputView.coffee
+++ components/inputs/KDInputView.coffee
@@ -332,7 +332,7 @@
       $growCalculator.detach()
       @$()[0].style.height = "none" # hack to set to initial

-    @on "keydown", =>
+    resize = =>
       $growCalculator.html @getValue().replace /\n/gm, "<br />"
       height = $growCalculator.height()
       if @$().css('box-sizing') is "border-box"
@@ -341,6 +341,9 @@
         height     = height + border + padding

       @setHeight height
+
+    @on "keydown", => resize()
+    @on "keyup", (event) => resize() if @getValue().length is 0

   enableTabKey:-> @inputTabKeyEnabled = yes


Fixed version request for good !

Fixed version request for good !

--- src/ubs.coffee
+++ src/ubs.coffee
@@ -13,7 +13,6 @@
 sprintf = require 'sprintf'
 util = require 'util'
 vm = require 'vm'
-CoffeeScript = require 'coffee-script'
 yaml = require 'js-yaml'
 _ = require 'underscore'

@@ -23,6 +22,8 @@
 {Config} = require "./config"
 Dispatch = require "./dispatch"
 plugins = require "./plugins"
+
+exports.VERSION = VERSION = require('../package.json').version

 SETTING_RE = /^(\w[-\.\w]*)=(.*)$/
 TASK_REGEX = /^[a-z][-a-z0-9_]*$/
@@ -184,8 +185,6 @@
 showHelp = ->
   console.log HELP
   0
-
-exports.VERSION = VERSION = JSON.parse(fs.readFileSync('package.json', 'utf8')).version

 HELP = """
 ubs #{VERSION}


[locale] ru

[locale] ru

--- app/scripts/nls/ru/locale.coffee
+++ app/scripts/nls/ru/locale.coffee
@@ -4,7 +4,73 @@
     my_location: 'Мое место'
     enter_location: 'Введите место (Москва)'
     play: 'Играть'
+    help: 'Помощь'
   tags:
     track: 'трэк'
     artist: 'исполнитель'
     album: 'альбом'
+  share:
+    title: 'Музыка погоды'
+    description: ''
+    share: 'Поделиться'
+  help:
+    tabs: ['how_it_works', 'credits']
+    links:
+      how_it_works: 'Как это работает'
+      credits: 'Провайдеры'
+    content:
+      how_it_works: '''
+

+
Введите ваше местоположение в поле сверху

+
... или нажмите на кнопку с глобусом, чтобы определить его автоматически

+
Нажмите кнопку 'Играть', чтобы получить прогноз погоды

+
Слушайте музыку, передающую настроение погоды сегодня

+
Просмотрите прогноз на следующие 5 дней

+
Поделитесь этим с друзьями

+

+        '''
+      credits: '''
+

+
Музыка:
+            Jamendo
+

+
Прогноз погоды:
+            OpenWeatherMap
+

+
Фоновые картинки:
+            Panoramio
+

+

+
PlayWeather не отвечает за информацию, полученную из внешних источников.

+
PlayWeather на стадии бета версии. Могут случаться сбои.

+        '''
+  weather_info:
+    today: 'Сегодня'
+  weather_mood:
+    hot: 'Жарко'
+    warm: 'Тепло'
+    cool: 'Прохладно'
+    cold: 'Холодно'
+    frost: 'Мороз'
+    clear: 'Ясно'
+    cloudy: 'Облачно'
+    overcast: 'Тучи'
+    drizzle: 'Моросит'
+    light_rain: 'Небольшой дождь'
+    rain: 'Дождь'
+    shower: 'Сильный дождь'
+    light_snow: 'Небольшой снег'
+    snow: 'Снег'
+    heavy_snow: 'Сильный снег'
+    breeze: 'Ветерок'
+    gale: 'Ветер'
+    storm: 'Шторм'
+    hurricane: 'Ураган'
+    mist: 'Туман'
+    smoke: 'Дымка'
+    haze: 'Дымка'
+    dust: 'Пыль'
+    fog: 'Густой туман'
+  buttons:
+    hide: 'Скрыть'
+    share: 'Поделиться'


Add German

Add German

--- app/index.coffee
+++ app/index.coffee
@@ -52,6 +52,7 @@
   translations:
     en: label: 'English', strings: enUs
     zh_tw: label: "繁體中文 Chinese (Traditional)", strings: './translations/zh-tw.json'
+    de: label: "Deutsch", strings: "./translations/de.json"

 languageManager.on 'change-language', (e, code, strings) ->
   translate.load strings


Tweak message when prompting for a directory

--- src/packages/tree-view/src/tree-view.coffee
+++ src/packages/tree-view/src/tree-view.coffee
@@ -208,9 +208,13 @@
     entry = @selectedEntry()
     return unless entry
     oldPath = entry.getPath()
+    if fs.isFile(oldPath)
+      prompt = "Enter the new path for the file."
+    else
+      prompt = "Enter the new path for the directory."

     dialog = new Dialog
-      prompt: "Enter the new path for the file."
+      prompt: prompt
       path: @rootView.project.relativize(oldPath)
       select: true
       iconClass: 'move'


changed port to 8000 -- the only available one at salzburg research office...

--- nodejs/palsu.coffee
+++ nodejs/palsu.coffee
@@ -18,7 +18,7 @@
 server.get '/', (request, response) ->
     response.sendfile "#{process.cwd()}/index.html"

-server.listen(8002)
+server.listen(8000)

 # ## Handling sockets
 socket = io.listen server


deving iPad

deving iPad

--- app/coffee/metamenu.coffee
+++ app/coffee/metamenu.coffee
@@ -19,6 +19,7 @@
   clickEvent = if (ua.match(/iPad/i)) then "touchstart" else "click"

   $('.settingsPanel .trigger').first().bind( clickEvent, (event)->
+    alert "clicked"
     $.publish('toggleSettings', [this])
   )


fixing the bedsheet

fixing the bedsheet

--- app/coffee/metamenu.coffee
+++ app/coffee/metamenu.coffee
@@ -117,25 +117,25 @@
     # $.publish('follow/changing', [node])
     $.getJSON("/images.json?section=Bedsheets", (data) =>

-    @$currentMenuPanel.find('.bedsheets ul').html('');
-
-    # Add elements for each bedsheet returned
-    for node in data
-      newElement = '

'
-      @$currentMenuPanel.find('.bedsheets ul').append(newElement)
-
-    @$currentMenuPanel.find('.bedsheets ul').find('li').click (event) =>
-      # SUPER TEMP
+      @$currentMenuPanel.find('.bedsheets ul').html('');
+
+      # Add elements for each bedsheet returned
+      for node in data
+        newElement = '

'
+        @$currentMenuPanel.find('.bedsheets ul').append(newElement)
+
+      @$currentMenuPanel.find('.bedsheets ul').find('li').click (event) =>
+        # SUPER TEMP

-      bedsheetUrl = 'url("/images/uploads/' + $(event.currentTarget).data('id') + '-bedsheet.jpg")'
-      $('#body').css('background-image', bedsheetUrl)
-
-      if $('#entry_view_preference_attributes_image_id').attr('name')?
-        @updateEntryBedsheet($(event.currentTarget).data('id'))
-      else
-        @updateUserBedsheet($(event.currentTarget).data('id'))
+        bedsheetUrl = 'url("/images/uploads/' + $(event.currentTarget).data('id') + '-bedsheet.jpg")'
+        $('#body').css('background-image', bedsheetUrl)
+
+        if $('#entry_view_preference_attributes_image_id').attr('name')?
+          @updateEntryBedsheet($(event.currentTarget).data('id'))
+        else
+          @updateUserBedsheet($(event.currentTarget).data('id'))

-    )
+      )

   updateUserBedsheet: (@bedsheet_id)->
     $.ajax {
@@ -233,8 +233,6 @@
         when "Friends only" then sharingLevel = 200
         when "Anonymous" then sharingLevel = 50
         when "Private" then sharingLevel = 0
-
-    alert typeof sharingLevel

     $.ajax {
       type: 'PUT'


Documentation

Documentation

--- src/point.coffee
+++ src/point.coffee
@@ -277,6 +277,25 @@
     ###*
     The norm of a vector is the unit vector pointing in the same direction. This method
     treats the point as though it is a vector from the origin to (x, y).
+
+

+       point = Point(2, 3).norm()
+
+       point.x
+    => 0.5547001962252291
+
+       point.y
+    => 0.8320502943378437
+
+       anotherPoint = Point(2, 3).norm(2)
+
+       anotherPoint.x
+    => 1.1094003924504583
+
+       anotherPoint.y
+    => 1.6641005886756874
+

+
     @name norm
     @methodOf Point#


patch a bug of oversight

patch a bug of oversight

--- node_modules_koding/bongo-client/src/eventbus.coffee
+++ node_modules_koding/bongo-client/src/eventbus.coffee
@@ -20,7 +20,6 @@
       genericRoutingKey = getGenericRoutingKey name
       channel = @channels[name] = @mq.subscribe genericRoutingKey, isReadOnly: yes
     else
-      @counts[name] += 1
       channel = @channels[name]
     channel.on event, @dispatch.bind this, getRoutingKey name, event
     @counts[name]++


Interpofail

Interpofail

--- src/robot.coffee
+++ src/robot.coffee
@@ -219,7 +219,7 @@
   #
   # Returns nothing.
   loadAdapter: (path, adapter) ->
-    @logger.debug 'Loading adapter #{adapter}'
+    @logger.debug "Loading adapter #{adapter}"

     try
       path = if adapter in HUBOT_DEFAULT_ADAPTERS


whitespace cleanup

whitespace cleanup

--- src/robot.coffee
+++ src/robot.coffee
@@ -212,7 +212,7 @@
     lowerFuzzyName = fuzzyName.toLowerCase()
     user for key, user of (@brain.data.users or {}) when (
       user.name.toLowerCase().lastIndexOf(lowerFuzzyName, 0) == 0)
-
+
   # Public: If fuzzyName is an exact match for a user, returns an array with
   # just that user. Otherwise, returns an array of all users for which
   # fuzzyName is a raw fuzzy match (see usersForRawFuzzyName).
@@ -224,7 +224,7 @@
     # will include exact matches
     for user in matchedUsers
       return [user] if user.name.toLowerCase() is lowerFuzzyName
-
+
     matchedUsers

   run: ->


fix(sidebar): Properly initialize sidebar accounts

--- src/flux/stores/focused-perspective-store.coffee
+++ src/flux/stores/focused-perspective-store.coffee
@@ -113,6 +113,9 @@

   sidebarAccounts: =>
     {sidebarAccountIds} = NylasEnv.savedState
-    sidebarAccountIds.map((id) => AccountStore.accountForId(id))
+    if  sidebarAccountIds
+      sidebarAccountIds.map((id) => AccountStore.accountForId(id))
+    else
+      AccountStore.accounts()

 module.exports = new FocusedPerspectiveStore()


Fix not returning correct position when selection does not end at text length.

--- src/selix.coffee
+++ src/selix.coffee
@@ -1,7 +1,7 @@
 # selix - github.com/bilde - MIT License

 lineHandler = (elem, position) ->
-  position - (elem.value.slice(0, position).split('\r\n').length - 1)
+  elem.value.slice(0, position).split('\r\n').length - 1

 getCaret = (elem) ->
   if typeof elem.selectionStart is 'number' and typeof elem.selectionEnd is 'number'
@@ -22,12 +22,14 @@
     else
       # moveStart and moveEnd returns how many characters it moved
       start = - elemRange.moveStart('character', - elem.value.length)
+      start += lineHandler(elem, start)
       # Check if the caret ends at the end of the text
       if elemRange.compareEndPoints('EndToEnd', endRange) >= 0
         end = elem.value.length
       else
         # Finally we calculate the end position
         end = - elemRange.moveEnd('character', - elem.value.length)
+        end += lineHandler(elem, end)
     # Return the selection as an object
     start: start,
     end: end
@@ -42,8 +44,8 @@
     # Move the range to the start of the element
     range.collapse true
     # Move the positions to the place specified in characters
-    range.moveEnd 'character', lineHandler(elem, end)
-    range.moveStart 'character', lineHandler(elem, start)
+    range.moveEnd 'character', end - lineHandler(elem, end)
+    range.moveStart 'character', start - lineHandler(elem, start)
     # Select the range so that it is visible
     range.select()


Cache Shape drawing

Cache Shape drawing

--- src/shape.coffee
+++ src/shape.coffee
@@ -21,20 +21,21 @@
       y: 0
     @solid = false
     @path = null # custom draw function
+    @canvas = document.createElement 'canvas' # Internal shape cache
+
+    @generateCache()

   ###
-   * @description Draw object
-   * @param {CanvasRenderingContext2D} context
-  ###
-  draw: (context, offsetX = 0, offsetY = 0) ->
-    offsetX = offsetY = 0 if @fixed
+  @description Draw object onto internal <canvas> cache
+  ###
+  generateCache: ->
+    # TODO: resize to handle shadow
+    @canvas.setAttribute 'width', @size + @lineWidth
+    @canvas.setAttribute 'height', @size + @lineWidth

-    # Draw child objects first, so they will be on the "bottom"
-    super context, @position.x + offsetX, @position.y + offsetY
-
-    context.translate @position.x + offsetX, @position.y + offsetY
-    context.scale @scale, @scale if @scale != 1
-    context.rotate @rotation if @rotation != 0 && @rotation != Math.PI * 2
+    context = @canvas.getContext '2d'
+    context.lineWidth = @lineWidth
+    context.lineJoin = @lineJoin

     if @shadow.x != null and @shadow.y != null and @shadow.blur != null and @shadow.color != null
       context.shadowOffsetX = @shadow.x
@@ -42,15 +43,13 @@
       context.shadowBlur = @shadow.blur
       context.shadowColor = @shadow.color

-    context.globalAlpha = @alpha if @alpha < 1
-    context.lineWidth = @lineWidth if context.lineWidth != @lineWidth
-    context.lineJoin = @lineJoin if context.lineJoin != @lineJoin
-
     # Allow sprite objects to have custom draw functions
     if @path != null
         @path(context)
     else
       context.beginPath()
+      # TODO: Handle shadow offset
+      context.translate @size / 2 + @lineWidth / 2, @size / 2 + @lineWidth / 2 # Move to center of canvas

       switch @vertices
         when 0
@@ -70,11 +69,30 @@
       context.closePath()

       if @solid
-        context.fillStyle = @color if context.fillStyle != @color
+        context.fillStyle = @color
         context.fill()
       else
-        context.strokeStyle = @color if context.strokeStyle != @color
+        context.strokeStyle = @color
         context.stroke()
+
+  ###
+  @description Draw object
+  @param {CanvasRenderingContext2D} context
+  ###
+  draw: (context, offsetX = 0, offsetY = 0) ->
+    offsetX = offsetY = 0 if @fixed
+
+    # Draw child objects first, so they will be on the "bottom"
+    super context, @position.x + offsetX, @position.y + offsetY
+
+    # Set scale/rotation/alpha
+    context.translate @position.x + offsetX, @position.y + offsetY
+    context.scale @scale, @scale if @scale != 1
+    context.rotate @rotation if @rotation != 0 && @rotation != Math.PI * 2
+    context.globalAlpha = @alpha if @alpha < 1
+
+    # Draw vector shape cache
+    context.drawImage @canvas, -@size / 2, -@size / 2

     # Reset scale/rotation/alpha
     context.rotate -@rotation if @rotation != 0 && @rotation != Math.PI * 2


Use a temporary stored procedure for testing procedure return value. (Instead of relying on a procedure that is probably, but not necessarily, on the server and available.)

--- test/integration/rpc-test.coffee
+++ test/integration/rpc-test.coffee
@@ -64,18 +64,14 @@
     #console.log(text)
   )

-exports.execFailedProc = (test) ->
-  test.expect(5)
+exports.procReturnValue = (test) ->
+  test.expect(3)

   config = getConfig()

-  request = new Request('sp_help', (err) ->
-    test.ok(err)
-
+  request = new Request('#test_proc', (err) ->
     connection.close()
   )
-
-  request.addParameter(TYPES.NVarChar, 'objname', 'bad')

   request.on('doneProc', (rowCount, more, returnStatus) ->
     test.ok(!more)
@@ -86,14 +82,18 @@
     test.ok(more)
   )

-  request.on('row', (columns) ->
-    test.ok(false)
-  )
-
   connection = new Connection(config)

   connection.on('connect', (err) ->
-    connection.callProcedure(request)
+    execSql(test, connection,
+      "
+        CREATE PROCEDURE #test_proc
+        AS
+          return 1
+      ",
+      ->
+        connection.callProcedure(request)
+    )
   )

   connection.on('end', (info) ->


Could be working code with GeoCodeMe

Could be working code with GeoCodeMe

- Need to get it installed on Heroku to test
- Need to get GeoCodeMe working then can do the Ingress stuff

--- scripts/ingress-map.coffee
+++ scripts/ingress-map.coffee
@@ -1,13 +1,32 @@
 # Description:
-#   Example scripts for you to examine and try out.
+#   Interacts with the Google Maps API and displays an Ingress map of the area in the query.
 #
-# Notes:
-#   They are commented out by default, because most of them are pretty silly and
-#   wouldn't be useful and amusing enough for day to day huboting.
-#   Uncomment the ones you want to try and experiment with.
-#
-#   These are from the scripting documentation: https://github.com/github/hubot/blob/master/docs/scripting.md
+# Commands:
+#   hubot ingressmap <query> - Returns a map view of the area returned by `query`.

 module.exports = (robot) ->
-  robot.hear /map/i, (msg) ->
-    msg.send "https://www.ingress.com/intel?ll=36.066168,-86.928635&z=12"
+
+  robot.respond /ingressmap (.*)/i, (msg) ->
+    query = msg.match[1]
+    key   = process.env.HUBOT_GOOGLE_API_KEY
+
+    if !key
+      msg.send "Please enter your Google API key in the environment variable HUBOT_GOOGLE_API_KEY."
+
+    geocodeMe msg, query, (text) ->
+      msg.reply text
+    #msg.reply "https://www.ingress.com/intel?ll=36.066168,-86.928635&z=12"
+
+geocodeMe = (msg, query, cb) ->
+  msg.http("https://maps.googleapis.com/maps/api/geocode/json")
+    .header('User-Agent', 'Hubot Geocode Location Engine')
+    .query({
+      address: query
+      sensor: false
+    })
+    .get() (err, res, body) ->
+      response = JSON.parse(body)
+      return cb "No idea. Tried using a map? https://maps.google.com/" unless response.results?.length
+
+      location = response.results[0].geometry.location.lat + "," + response.results[0].geometry.location.lng
+      cb "That's somewhere around " + location + " - https://maps.google.com/maps?q=" + location


remove navbar elements

remove navbar elements

--- src/scripts/components/partials/navbar.cjsx
+++ src/scripts/components/partials/navbar.cjsx
@@ -17,14 +17,6 @@

-

-

-              Home
-

-

-              Workouts
-

-


Fix proxy bug

Fix proxy bug

--- views/components/settings/parts/proxy-config.cjsx
+++ views/components/settings/parts/proxy-config.cjsx
@@ -32,7 +32,7 @@
         config.set 'proxy.shadowsocks.password', @refs.shadowsocksPassword.getValue()
         config.set 'proxy.shadowsocks.method', @refs.shadowsocksMethod.getValue()
       else
-        config.set 'use', 'none'
+        config.set 'proxy.use', 'none'
     toggleModal '代理设置', '保存成功'
     e.preventDefault()
   handleHttpHostChange: (e) ->


Redirect to activities in the index route instead of the application route.

--- src/main/coffeescript/modules/application.coffee
+++ src/main/coffeescript/modules/application.coffee
@@ -1,5 +1,5 @@
 define ['app', 'ember', 'text!/templates/application.hbs'], (App, Ember, applicationTemplate) ->
 	App.registerTemplate 'application', applicationTemplate

-	App.ApplicationRoute = Ember.Route.extend
-		beforeModel: -> @transitionTo '/activities'
+	App.IndexRoute = Ember.Route.extend
+		redirect: -> @transitionTo 'activities'


Update <trix-editor value="..."> on each change

--- src/trix/controllers/editor_element_controller.coffee
+++ src/trix/controllers/editor_element_controller.coffee
@@ -45,7 +45,9 @@

   save: ->
     if serializer = serializers[@contentType]
-      @inputElement.value = serializer.call(this)
+      value = serializer.call(this)
+      @inputElement.value = value
+      @element.setAttribute("value", value)
     else
       throw new Error "unknown content type: '#{@contentType}'"


Test disabling on change

Test disabling on change

--- test/unit/test_task_lists.coffee
+++ test/unit/test_task_lists.coffee
@@ -64,3 +64,15 @@
   setTimeout ->
     start()
   , 20
+
+asyncTest "disables task list items when changing source", ->
+  expect 1
+
+  $(document).on 'tasklist:disabled', (event) ->
+    ok true
+
+  setTimeout ->
+    start()
+  , 20
+
+  @checkbox.click()


Use errorResponseHandler as a props instead of context

--- containers/mixins/Handler.jsx.coffee
+++ containers/mixins/Handler.jsx.coffee
@@ -14,15 +14,13 @@
   #       callback()
   #

-  contextTypes:
-    errorResponseHandler: React.PropTypes.object.isRequired
-
   propTypes:
-    title:        React.PropTypes.string
-    submitting:   React.PropTypes.bool
-    serverErrors: React.PropTypes.object
-    formData:     React.PropTypes.object
-    buttons:      React.PropTypes.object
+    title:                React.PropTypes.string
+    submitting:           React.PropTypes.bool
+    serverErrors:         React.PropTypes.object
+    formData:             React.PropTypes.object
+    buttons:              React.PropTypes.object
+    errorResponseHandler: React.PropTypes.object

   getInitialState: ->
     title            : @props.title        ? ""
@@ -65,7 +63,7 @@
         @setState serverErrors: response.body, submitting: false
     else
       @setState(submitting: false)
-      @context.errorResponseHandler.resolve(response)
+      @props.errorResponseHandler.resolve(response)

   clearForm: ->
     if @isMounted()


Consider submit successfull if number of submits changed even if informatics responded with 500

--- server/api/setupApi.coffee
+++ server/api/setupApi.coffee
@@ -78,11 +78,18 @@
         res.json({loggedOut: true})

     app.post '/api/submit/:problemId', ensureLoggedIn, wrap (req, res) ->
+        fullProblemId = "p" + req.params.problemId
         try
-            informaticsUser = await InformaticsUser.getUser(req.user.informaticsUsername, req.user.informaticsPassword)
-            informaticsData = await informaticsUser.submit(req.params.problemId, req.get('Content-Type'), req.body)
-        finally
-            await downloadSubmits.runForUser(req.user.informaticsId, 5, 1)
+            oldSubmits = await Submit.findByUserAndProblem(req.user.informaticsId, fullProblemId)
+            try
+                informaticsUser = await InformaticsUser.getUser(req.user.informaticsUsername, req.user.informaticsPassword)
+                informaticsData = await informaticsUser.submit(req.params.problemId, req.get('Content-Type'), req.body)
+            finally
+                await downloadSubmits.runForUser(req.user.informaticsId, 5, 1)
+        catch e
+            newSubmits = await Submit.findByUserAndProblem(req.user.informaticsId, fullProblemId)
+            if oldSubmits.length == newSubmits.length
+                throw e
         res.json({submit: true})

     app.post '/api/submit/:problemId/draft', ensureLoggedIn, wrap (req, res) ->


Remove hardcoded username from users.js.coffee.

--- app/assets/javascripts/application/routers/users.js.coffee
+++ app/assets/javascripts/application/routers/users.js.coffee
@@ -13,7 +13,7 @@

   edit: ->
     username = $('body').attr('id');
-    user = new App.Models.User id: "supermario"
+    user = new App.Models.User id: username
     user.fetch().done ->
       view = new App.Views.Users.Edit(model: user)
       $('.main-container').html(view.el)


remove stray quote from navigation_controls

--- app/assets/javascripts/components/navigation_controls.cjsx
+++ app/assets/javascripts/components/navigation_controls.cjsx
@@ -94,5 +94,5 @@
       alert("""
         Sorry! You can't move onto other pages yet, as there are some required questions without answers.

-        #{QuestionnaireStore.unansweredQuestionsForPage(NavigationStore.currentPage()).join("\n")}"
+        #{QuestionnaireStore.unansweredQuestionsForPage(NavigationStore.currentPage()).join("\n")}
       """)


fix typo in search results

fix typo in search results

[#97634864]

--- app/assets/javascripts/components/search/results.js.coffee
+++ app/assets/javascripts/components/search/results.js.coffee
@@ -29,7 +29,7 @@
   render: ->
     (div {id: 'offering_list'},
       (p {style: {fontWeight: 'bold'}},
-        @renderMessage(), ' matching ', @renderSearchTerm(), ' slected criteria'
+        @renderMessage(), ' matching ', @renderSearchTerm(), ' selected criteria'
       )
       (div {className: 'results_container'},
         @renderAllResults()


make notifications more consistent

make notifications more consistent

--- app/assets/javascripts/controllers/notifications.js.coffee
+++ app/assets/javascripts/controllers/notifications.js.coffee
@@ -11,6 +11,7 @@
     setTimeout ->
       $scope.notifications.splice(0, 0, {time: Date.now(), message: 'Hey, Sorry for your trouble. We\'ll send someone to pick up your garbage as soon as we can. - management'})
       Settings.save()
+      $scope.$apply()
     , 2000

   $scope.$on '$destroy', ->


conditional logic working with multiple conditions whether any or all

--- app/assets/javascripts/hanuman/conditional-logic.js.coffee
+++ app/assets/javascripts/hanuman/conditional-logic.js.coffee
@@ -51,52 +51,36 @@
   bindConditions: (ancestor_id, $triggerElement, operator, answer) ->
     $triggerElement.on "change", ->
       # pop out of condition into rules to handle all conditions defined in the rule
-      rules = []
       $("[data-rule]").each ->
         $ruleElement = $(this)
-        #if $ruleElement.attr('data-rule').length > 0
         rule = $.parseJSON($ruleElement.attr("data-rule")).rule
-        rules.push rule
         matchType = rule.match_type
-
-      questionId = $triggerElement.closest('.form-entry-item-container').attr('data-question-id')
-      conditions = rules.map((rule) ->
-        JSON.stringify rule.conditions
-      )
-      currentConditions = JSON.parse(_.filter(conditions, (s) ->
-        s.indexOf('question_id":' + String(questionId)) != -1
-      )[0])
-      if currentConditions.length > 1
-        console.log currentConditions
-        # loop through and run hide show questions for each condition
-        currentConditions.map (condition) ->
-          console.log condition
-          questionId = condition.question_id
-          $triggerContainer = $("[data-question-id=" + questionId + "]")
-          $triggerElement = $triggerContainer.find(".form-control")
-          # deal with any condition, once we get a hide_questions = false then we don't need to run through the rules
-          hideQuestions = self.evaluateCondition(condition.operator, condition.answer, self.getValue($triggerElement))
-          # deal with any condition, once we get a hide_questions = false then we don't need to run through the rules
-          if matchType == "any" and hideQuestions == false
-            console.log "let's break out of this joint"
-          # ancestorId = rule.question_id
-          # # text, textarea, select
-          # if $triggerElement.length < 2
-          #   #self.setDefaultState(rule.question_id, $triggerElement, condition.operator, condition.answer)
-          #   self.hideShowQuestions(hideQuestions, ancestorId)
-          # # radio buttons
-          # else
-          #   if $triggerElement.is(":checkbox")
-          #     # limit binding of each checkbox if data-label-value and answer are the same-kdh
-          #     $triggerElement = $triggerContainer.find(".form-control[data-label-value=" + condition.answer + "]")
-          #     self.hideShowQuestions(hideQuestions, ancestorId)
-          #   else
-          #     for element in $triggerElement
-          #       do (element) ->
-          #         #self.setDefaultState(rule.question_id, $(element), condition.operator, condition.answer)
-          #         self.hideShowQuestions(hideQuestions, ancestorId)
-      else
-        self.hideShowQuestions(self.evaluateCondition(operator, answer, self.getValue($triggerElement)), ancestor_id)
+        questionId = $triggerElement.closest('.form-entry-item-container').attr('data-question-id')
+        conditions = rule.conditions
+        ancestorId = rule.question_id
+        matchingCondition = _.where(conditions, {question_id: Number(questionId)})
+        if matchingCondition.length > 0
+          if conditions.length > 1
+            conditionMetTracker = []
+            _.each conditions, (condition) ->
+              $conditionElement = $("[data-question-id=" + condition.question_id + "]").find('.form-control')
+              hideQuestions = self.evaluateCondition(condition.operator, condition.answer, self.getValue($conditionElement))
+              conditionMet = !hideQuestions
+              conditionMetTracker.push conditionMet
+            # match type any (or)
+            if matchType == "any"
+              if _.indexOf(conditionMetTracker, true) > 0
+                self.hideShowQuestions(false, ancestorId)
+              else
+                self.hideShowQuestions(true, ancestorId)
+            # match type all
+            if matchType == "all"
+              if _.indexOf(conditionMetTracker, false) == -1
+                self.hideShowQuestions(false, ancestorId)
+              else
+                self.hideShowQuestions(true, ancestorId)
+          else
+            self.hideShowQuestions(self.evaluateCondition(operator, answer, self.getValue($triggerElement)), ancestor_id)
     return

   #hide or show questions


changes anjular config

changes anjular config

--- app/assets/javascripts/hours_report/hours_report.js.coffee
+++ app/assets/javascripts/hours_report/hours_report.js.coffee
@@ -3,12 +3,11 @@

 @App = angular.module("ExpenseReportApp", ["rails", "ngRoute", "ngAnimate", "mgcrea.ngStrap", "cc", "ui.bootstrap.showErrors"])

-@App.config ["$httpProvider", (provider) ->
+@App.config ["$httpProvider", 'RailsResourceProvider', (provider, railsResourceProvider) ->
+  railsResourceProvider.updateMethod('patch')
   provider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest'
   provider.defaults.headers.common["X-CSRF-Token"] = $("meta[name=csrf-token]").attr("content")
 ]
-@App.config (RailsResourceProvider) ->
-  RailsResourceProvider.updateMethod('patch')

 window.HoursReport =
   configs:


Add database fallback for Template.find

Add database fallback for Template.find

--- server/lib/template.coffee
+++ server/lib/template.coffee
@@ -33,6 +33,7 @@
   class Template

     find: (callback) ->
+      self = @
       # Return all Handlebars paths
       glob '**/*.hbs', {cwd: cwd}, (err, files) ->
         return callback(err) if err
@@ -52,16 +53,32 @@

         for filename, i in files
           do (filename, i) ->
-            fs.readFile cwd + filename, 'utf8', (err, contents) ->
+            f = filename.replace(/\.hbs$/,'')
+            self.read(f, (err, contents) ->
               if err
-                check err
-              else
-                items[i] = {filename: filename.replace(/\.hbs$/,''), contents: contents}
-                check()
+                return check(err)
+              items[i] = { filename: f, contents: contents }
+              check()
+            )
         check()

     read: (filename, callback) ->
-      fs.readFile resolve(filename + '.hbs'), 'utf8', callback
+      filename = resolve(filename)
+      fs.readFile(filename + '.hbs', 'utf8', (err, contents) ->
+        if err
+          if err.code == 'ENOENT'
+            DbTemplate.findOne({ filename: filename }, (dbErr, template) ->
+              if dbErr
+                return callback(dbErr)
+              if not template
+                return callback(err)
+              callback(null, template.contents)
+            )
+          else
+            callback(err)
+        else
+          callback(null, contents)
+      )

     write: (filename, contents, callback) ->
       filename = resolve(filename+'.hbs')


Fix discover search for ie8

Fix discover search for ie8

--- app/assets/javascripts/neighborly/discover/index.js.coffee
+++ app/assets/javascripts/neighborly/discover/index.js.coffee
@@ -61,7 +61,7 @@
         tags = _.map $tags, (t) -> $(t).data('name')
         url += "/tags/#{tags.join()}"

-      if $search.val() != ''
+      if $search.val() != '' && $search.val() != 'Search...'
         url += "/search/#{$search.val()}"
         this.addResultFor('search', $search.val())
       else


Revert "Replaces variant name with product name"

This reverts commit 1f380c5c30d0b9b9b366fdc8fae5986974d24525.

--- app/assets/javascripts/sprangular/services/checkout.coffee
+++ app/assets/javascripts/sprangular/services/checkout.coffee
@@ -100,7 +100,7 @@
       for item in order.items
         ga "ecommerce:addItem",
           id:       order.number
-          name:     item.variant.product.name
+          name:     item.variant.name
           sku:      item.variant.sku
           price:    item.price
           quantity: item.quantity


Remove debug log.

Remove debug log.

--- src/spec/integration/integration-order.spec.coffee
+++ src/spec/integration/integration-order.spec.coffee
@@ -141,8 +141,6 @@
       expect(response.statusCode).toBe 200
       console.error body unless response.statusCode is 200
       orderUpdated = JSON.parse(body)
-
-      console.log "orderUpdated #{JSON.stringify orderUpdated, null, ' '}"

       expect(orderUpdated).toBeDefined()
       expect(_.size orderUpdated.shippingInfo.deliveries).toBe 1


webapp errors: try to serialize unknown unhandledrejection object

--- src/webapp-lib/webapp-error-reporter.coffee
+++ src/webapp-lib/webapp-error-reporter.coffee
@@ -299,7 +299,8 @@
         # just to make sure there is a message
         reason = e.reason ? '<no reason>'
         if typeof(reason) == 'object'
-            reason = "#{reason.stack ? reason.message ? ''}"
+            misc = require('smc-util/misc')
+            reason = "#{reason.stack ? reason.message ? misc.trunc_middle(misc.to_json(reason), 1000)}"
         e.message = "unhandledrejection: #{reason}"
         reportException(e, "unhandledrejection")


Add missing id to paper test data.

Add missing id to paper test data.

The associated tests were throwing an error when the paper id
was undefined.

--- test/javascripts/views/paper_edit_view_test.js.coffee
+++ test/javascripts/views/paper_edit_view_test.js.coffee
@@ -5,6 +5,7 @@

   setup: ->
     paper = Ember.Object.create
+      id: 5
       title: ''
       shortTitle: 'Does not matter'
       body: 'hello'


StacksCustomViews: stepsView implementation for showing each configure step

--- client/admin/lib/views/stacks/stackscustomviews.coffee
+++ client/admin/lib/views/stacks/stackscustomviews.coffee
@@ -32,3 +32,48 @@

     button: (options) ->
       new kd.ButtonView options
+
+
+    wizardView: (data) =>
+      @views.stepsView 1
+
+
+    step: (options) =>
+
+      {title, step, selected} = options
+
+      container = @views.container "#{if selected then 'selected' else ''}"
+
+      @addTo container,
+        text_step  : step
+        text_title : title
+
+      return container
+
+
+    stepsView: (options) =>
+
+      if typeof options is 'number'
+        steps = [
+          { title : 'Select Repo' }
+          { title : 'Locate File' }
+          { title : 'File' }
+          { title : 'Test Integrations' }
+        ]
+        selected  = options
+      else
+        { steps } = options
+
+      container = @views.container 'steps-view'
+
+      @addTo container, view :
+        cssClass : 'vline'
+        tagName  : 'cite'
+
+      steps.forEach (step, index) =>
+        step.step = index + 1
+        if selected? and selected is step.step
+          step.selected = yes
+        @addTo container, { step }
+
+      return container


StacksCustomViews: fetchAndShowCredentialData: no need to shadow these anymore

--- client/admin/lib/views/stacks/stackscustomviews.coffee
+++ client/admin/lib/views/stacks/stackscustomviews.coffee
@@ -143,11 +143,6 @@
         outputView.addContent 'Failed: ', err.message
       else

-        # Hide sensitive information
-        provider = globals.config.providers[credential.provider]
-        (Object.keys provider.credentialFields).forEach (field) ->
-          data.meta[field] = '******************'
-
         try
           cred = JSON.stringify data.meta, null, 2
         catch e


DefineStackView: add CredentialStatusView

--- client/admin/lib/views/stacksv2/definestackview.coffee
+++ client/admin/lib/views/stacksv2/definestackview.coffee
@@ -7,6 +7,7 @@
 {yamlToJson}        = require './yamlutils'
 StackEditorView     = require './stackeditorview'
 updateStackTemplate = require './updatestacktemplate'
+CredentialStatusView= require './credentialstatusview'

 module.exports = class DefineStackView extends kd.View
@@ -24,10 +25,17 @@
     title   = stackTemplate?.title or 'Default stack template'
     content = stackTemplate?.template?.content

-    @inputTitle      = new kd.FormViewWithFields
-      fields         : title :
-        label        : 'Stack Template Title'
-        defaultValue : title
+    @inputTitle            = new kd.FormViewWithFields fields:
+      title                :
+        cssClass           : 'template-title'
+        label              : 'Stack Template Title'
+        defaultValue       : title
+        nextElement        :
+          credentialStatus :
+            cssClass       : 'credential-status'
+            itemClass      : CredentialStatusView
+
+    { @credentialStatus } = @inputTitle.inputs

     @editorView      = new StackEditorView {delegate: this, content}


login: activate querystring handling for register route

--- client/landing/site.landing/coffee/login/routes.coffee
+++ client/landing/site.landing/coffee/login/routes.coffee
@@ -86,8 +86,10 @@
     '/Login/:token?'    : handler (app, options)->
       app.getView().animateToForm 'login'
       app.handleQuery options
+    '/Register'         : handler (app, options)->
+      app.getView().animateToForm 'register'
+      app.handleQuery options
     '/Redeem'           : handler (app)-> app.getView().animateToForm 'redeem'
-    '/Register'         : handler (app)-> app.getView().animateToForm 'register'
     '/Reset'            : handler (app)-> app.getView().animateToForm 'reset'
     '/ResendToken'      : handler (app)-> app.getView().animateToForm 'resendEmail'
     '/Recover'          : handler (app)-> app.getView().animateToForm 'recover'


Use the existential operator to set the toolChain path from config

--- lib/toolchain-view.coffee
+++ lib/toolchain-view.coffee
@@ -16,9 +16,7 @@
   initialize: ->
     super
     @toolBinPath = atom.config.get("tokamak.toolBinPath")
-    configtoolchain = atom.config.get("tokamak.toolChain")
-    if configtoolchain
-      @toolChain = configtoolchain
+    @toolChain ?= atom.config.get("tokamak.toolChain")
     @getToolchainList(@items, @toolchainExitCallback)
     @commandSubscription = atom.commands.add 'atom-workspace',
     'tokamak:toolBinPath-select-toolchain': => @attach()


docs table of contents works after inter-docs navigation

--- client/code/view/docs.coffee
+++ client/code/view/docs.coffee
@@ -20,9 +20,10 @@
       , 250

   makePrettyLike: ->
-    prettyPrint()
-    $('nav.well').affix({offset: 110})
-    $('body').scrollspy()
+    prettyPrint() # syntax-highlight code blocks
+    $('nav.well').affix({offset: 110}) # fixed position for table of contents
+    $('body').scrollspy('refresh') # highlight links in table of contents on scroll
+    $(window).trigger('scroll') # fake a scroll event to highlight the current link

 class Cu.View.DeveloperDocs extends Cu.View.Docs
   template: 'docs-developer'


Add commentary to token-generator, and move token-alpha to top level

--- services/web/app/coffee/Features/Project/ProjectTokenGenerator.coffee
+++ services/web/app/coffee/Features/Project/ProjectTokenGenerator.coffee
@@ -1,14 +1,29 @@
 crypto = require 'crypto'
+
+# This module mirrors the token generation in Overleaf (`random_token.rb`),
+# for the purposes of implementing token-based project access, like the
+# 'unlisted-projects' feature in Overleaf

 module.exports = ProjectTokenGenerator =

+	# (From Overleaf `random_token.rb`)
+  #   Letters (not numbers! see generate_token) used in tokens. They're all
+  #   consonsants, to avoid embarassing words (I can't think of any that use only
+  #   a y), and lower case "l" is omitted, because in many fonts it is
+  #   indistinguishable from an upper case "I" (and sometimes even the number 1).
+	TOKEN_ALPHA: 'bcdfghjkmnpqrstvwxyz'
+
+	# Generate a 12-char token with only characters from TOKEN_ALPHA,
+	# suitable for use as a read-only token for a project
 	readOnlyToken: () ->
 		length = 12
-		tokenAlpha = 'bcdfghjkmnpqrstvwxyz'
+		tokenAlpha = ProjectTokenGenerator.TOKEN_ALPHA
 		result = ''
 		crypto.randomBytes(length).map( (a) -> result += tokenAlpha[a % tokenAlpha.length] )
 		return result

+	# Generate a longer token, with a numeric prefix,
+	# suitable for use as a read-and-write token for a project
 	readAndWriteToken: () ->
 		numerics = Math.random().toString().slice(2, 12)
 		token = ProjectTokenGenerator.readOnlyToken()


webapp/notify proj collabs: customize the email subject line with the name of the person inviting someone

--- src/smc-webapp/collaborators/add-to-project.cjsx
+++ src/smc-webapp/collaborators/add-to-project.cjsx
@@ -57,9 +57,12 @@

     invite_collaborator: (account_id) ->
         # project_id, account_id, body, subject, silent, replyto,  replyto_name
-        subject      = "CoCalc Invitation to #{@props.project.get('title')}"
         replyto      = redux.getStore('account').get_email_address()
         replyto_name = redux.getStore('account').get_fullname()
+        if replyto_name?
+            subject = "#{replyto_name} invites you to project #{@props.project.get('title')}"
+        else
+            subject = "CoCalc Invitation to project #{@props.project.get('title')}"
         @actions('projects').invite_collaborator(
             @props.project.get('project_id'),
             account_id,
@@ -93,9 +96,12 @@
         @setState(email_to: @state.search, email_body: body)

     send_email_invite: ->
-        subject      = "CoCalc Invitation to #{@props.project.get('title')}"
         replyto      = redux.getStore('account').get_email_address()
         replyto_name = redux.getStore('account').get_fullname()
+        if replyto_name?
+            subject = "#{replyto_name} invites you to project #{@props.project.get('title')}"
+        else
+            subject = "CoCalc Invitation to project #{@props.project.get('title')}"
         @actions('projects').invite_collaborators_by_email(@props.project.get('project_id'),
                                                                          @state.email_to,
                                                                          @state.email_body,


Clean up db a bit

Clean up db a bit

--- db.coffee
+++ db.coffee
@@ -43,10 +43,7 @@
 		PRIMARY KEY(userID, permission),
 		FOREIGN KEY(userID) REFERENCES users(userID)
 	);"
-	do ->
-		stmt = db.prepare "INSERT OR IGNORE INTO bot (key, value) VALUES (?, ?);"
-		stmt.run 'firstStart', Date.now()
-		do stmt.finalize
+	db.run "INSERT OR IGNORE INTO bot (key, value) VALUES (?, ?);", 'firstStart', Date.now()

 process.on 'exit', ->
 	winston.debug 'Closing database'


coffee abuse

coffee abuse

--- resources/assets/coffee/_classes/beatmapset-filter.coffee
+++ resources/assets/coffee/_classes/beatmapset-filter.coffee
@@ -41,6 +41,10 @@
     played: 'played'
     q: 'query'
     sort: 'sort'
+
+
+  @keyToChar: ->
+    @_keyToChar ||= _.invert @charToKey

   @defaults:
@@ -104,11 +108,10 @@
   @queryParamsFromFilters: (filters) ->
     return {} if !currentUser.id?

-    keyToChar = _.invert @charToKey
     charParams = {}

     for own key, value of filters
       if value? && @getDefault(filters, key) != value
-        charParams[keyToChar[key]] = value
+        charParams[@keyToChar()[key]] = value

     charParams


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/controllable.coffee
+++ src/controllable.coffee
@@ -5,6 +5,7 @@
   self.bind "update", ->
     if keydown.left
       I.x -= I.speed
+


Added Sand Yak name list, Ogre Scout M name list, and Ogre Scout F name list. Also added 10 names, 5 for Sand Yak, 3 for Ogre Scout M, and 2 for Ogre Scout F.

--- app/lib/world/names.coffee
+++ app/lib/world/names.coffee
@@ -414,3 +414,19 @@
   'Samurai': [
     'Hattori'
   ]
+  'Sand Yak': [
+    'Ofgar'
+    'Randall'
+    'Arngotho'
+    'Falthror'
+    'Langthok'
+  ]
+  'Ogre Scout M': [
+    'Frandar'
+    'Lanthon'
+    'Yorgalfen'
+  ]
+  'Ogre Scout F': [
+    'Freesa'
+    'Ralthora'
+  ]


Added more names for #53

Added more names for #53

--- app/lib/world/names.coffee
+++ app/lib/world/names.coffee
@@ -286,10 +286,19 @@
   ]
   "Potion Master": [
     "Snake"
+    "Amaranth"
+    "Zander"
+    "Arora"
   ]
   "Librarian": [
     "Hushbaum"
+    "Matilda"
+    "Agnes"
+    "Agathe"
   ]
   "Equestrian": [
     "Reynaldo"
-  ]
+    "Ryder"
+    "Thoron"
+    "Mirial"
+  ]


Modify SoundObj constructor

Modify SoundObj constructor

--- app/models/SoundObj.coffee
+++ app/models/SoundObj.coffee
@@ -3,7 +3,9 @@

 module.exports = class SoundObj extends Model

-  sourceNode: null
-  gainNode: null
-  buffer: null
-  isPlaying: false
+  constructor: ->
+    super
+    @sourceNode = null
+    @gainNode = null
+    @buffer = null
+    @isPlaying = false