SocialWorker: Tests: JUser: refactor and replace daisy with async.series

--- workers/social/lib/social/models/user/index.test.coffee
+++ workers/social/lib/social/models/user/index.test.coffee
@@ -5,7 +5,7 @@
 JSession  = require '../session'
 Speakeasy = require 'speakeasy'

-{ daisy
+{ async
   expect
   withDummyClient
   generateUserInfo
@@ -58,25 +58,23 @@

       queue = [

-        ->
+        (next) ->
           JUser.createUser userInfo, (err) ->
             expect(err).to.not.exist
-            queue.next()
-
-        ->
+            next()
+
+        (next) ->
           # setting a different email, username will be duplicate
-          userInfo.email  = generateRandomEmail()
-          expectedError   = "The slug #{userInfo.username} is not available."
+          userInfo.email = generateRandomEmail()
+          expectedError  = "The slug #{userInfo.username} is not available."

           JUser.createUser userInfo, (err) ->
             expect(err.message).to.be.equal expectedError
-            queue.next()
-
-        -> done()
+            next()

       ]

-      daisy queue
+      async.series queue, done

     it 'should pass error if email is in use', (done) ->
@@ -85,25 +83,23 @@

       queue = [

-        ->
+        (next) ->
           JUser.createUser userInfo, (err) ->
             expect(err).to.not.exist
-            queue.next()
-
-        ->
+            next()
+
+        (next) ->
           # setting a different username, email will be duplicate
           userInfo.username = generateRandomString()
           expectedError     = "Sorry, \"#{userInfo.email}\" is already in use!"

           JUser.createUser userInfo, (err) ->
             expect(err.message).to.be.equal expectedError
-            queue.next()
-
-        -> done()
+            next()

       ]

-      daisy queue
+      async.series queue, done

     describe 'when user data is valid', ->
@@ -112,44 +108,42 @@

         queue = [

-          ->
+          (next) ->
             # expecting user to be created
             JUser.createUser userInfo, (err) ->
               expect(err).to.not.exist
               # after the user is created, lower casing username because
               # we expect to see the username to be lower cased in jmodel documents
               userInfo.username = userInfo.username.toLowerCase()
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             # expecting user to be saved
             params = { username : userInfo.username }
             JUser.one params, (err, user) ->
               expect(err).to.not.exist
               expect(user.username).to.be.equal userInfo.username
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             # expecting account to be created and saved
             params = { 'profile.nickname' : userInfo.username }
             JAccount.one params, (err, account) ->
               expect(err).to.not.exist
               expect(account).to.exist
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             # expecting name to be created and saved
             params = { 'name' : userInfo.username }
             JName.one params, (err, name) ->
               expect(err).to.not.exist
               expect(name).to.exist
-              queue.next()
-
-          -> callback()
-
-        ]
-
-        daisy queue
+              next()
+
+        ]
+
+        async.series queue, callback

       it 'should be able to create user with lower case username', (done) ->
@@ -201,37 +195,35 @@

         queue = [

-          ->
+          (next) ->
             # expecting successful login
             JUser.login null, loginCredentials, (err) ->
               expect(err).to.not.exist
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             # keeping last login date
             JUser.one { username }, (err, user) ->
               expect(err).to.not.exist
               lastLoginDate = user.lastLoginDate
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             # expecting another successful login
             JUser.login null, loginCredentials, (err) ->
               expect(err).to.not.exist
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             # expecting last login date to be changed
             JUser.one { username }, (err, user) ->
               expect(err)           .to.not.exist
               expect(lastLoginDate) .not.to.be.equal user.lastLoginDate
-              queue.next()
-
-          -> callback()
-
-        ]
-
-        daisy queue
+              next()
+
+        ]
+
+        async.series queue, callback

       it 'should be able to login user with lower case username', (done) ->
@@ -267,36 +259,36 @@

         queue = [

-          ->
+          (next) ->
             # setting two factor authentication on by adding twofactorkey field
             JUser.update { username }, { $set: { twofactorkey: 'somekey' } }, (err) ->
               expect(err).to.not.exist
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             # trying to login with empty tf code
             loginCredentials.tfcode = ''
             JUser.login null, loginCredentials, (err) ->
               expect(err.message).to.be.equal 'TwoFactor auth Enabled'
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             # trying to login with invalid tfcode
             loginCredentials.tfcode = 'invalidtfcode'
             JUser.login null, loginCredentials, (err) ->
               expect(err.message).to.be.equal 'Access denied!'
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             # generating a 2fa key and saving it in mongo
             { base32 : tfcode } = Speakeasy.generate_key
               length    : 20
               encoding  : 'base32'
             JUser.update { username }, { $set: { twofactorkey: tfcode } }, (err) ->
               expect(err).to.not.exist
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             # generating a verificationCode and expecting a successful login
             verificationCode = Speakeasy.totp
               key       : tfcode
@@ -304,13 +296,11 @@
             loginCredentials.tfcode = verificationCode
             JUser.login null, loginCredentials, (err) ->
               expect(err).to.not.exist
-              queue.next()
-
-          -> done()
-
-        ]
-
-        daisy queue
+              next()
+
+        ]
+
+        async.series queue, done

     it 'should pass error if account is not found', (done) ->
@@ -320,29 +310,27 @@

       queue = [

-        ->
+        (next) ->
           # creating a new user with the newly generated userinfo
           JUser.createUser userInfo, (err, user) ->
             expect(err).to.not.exist
-            queue.next()
-
-        ->
+            next()
+
+        (next) ->
           # removing users account
           JAccount.remove { 'profile.nickname': username }, (err) ->
             expect(err).to.not.exist
-            queue.next()
-
-        ->
+            next()
+
+        (next) ->
           # expecting not to able to login without an account
           JUser.login null, loginCredentials, (err) ->
             expect(err.message).to.be.equal 'No account found!'
-            queue.next()
-
-        -> done()
+            next()

       ]

-      daisy queue
+      async.series queue, done

     it 'should check if the user is blocked', (done) ->
@@ -354,50 +342,48 @@

       queue = [

-        ->
+        (next) ->
           # creating a new user with the newly generated userinfo
           JUser.createUser userInfo, (err, user_) ->
             user = user_
             expect(err).to.not.exist
-            queue.next()
-
-        ->
+            next()
+
+        (next) ->
           # expecting successful login with the newly generated user
           JUser.login null, loginCredentials, (err) ->
             expect(err).to.not.exist
-            queue.next()
-
-        ->
+            next()
+
+        (next) ->
           # blocking user for 1 day
           untilDate = new Date(Date.now() + 1000 * 60 * 60 * 24)
           user.block untilDate, (err) ->
             expect(err).to.not.exist
-            queue.next()
-
-        ->
+            next()
+
+        (next) ->
           # expecting login attempt to fail and return blocked message
           JUser.login null, loginCredentials, (err) ->
             toDate = user.blockedUntil.toUTCString()
             expect(err.message).to.be.equal JUser.getBlockedMessage toDate
-            queue.next()
-
-        ->
+            next()
+
+        (next) ->
           # unblocking user
           user.unblock (err) ->
             expect(err).to.not.exist
-            queue.next()
-
-        ->
+            next()
+
+        (next) ->
           # expecting user to be able to login
           JUser.login null, loginCredentials, (err) ->
             expect(err).to.not.exist
-            queue.next()
-
-        -> done()
+            next()

       ]

-      daisy queue
+      async.series queue, done

     it 'should pass error if user\'s password needs reset', (done) ->
@@ -406,45 +392,43 @@
       { username, password }    = userInfo = generateUserInfo()
       loginCredentials          = generateCredentials { username, password }

-      setUserPasswordStatus = (queue, username, status) ->
+      setUserPasswordStatus = (username, status, callback) ->
         JUser.update { username }, { $set: { passwordStatus: status } }, (err) ->
           expect(err).to.not.exist
-          queue.next()
+          callback()

       queue = [

-        ->
+        (next) ->
           # creating a new user with the newly generated userinfo
           JUser.createUser userInfo, (err, user) ->
             expect(err).to.not.exist
-            queue.next()
-
-        ->
-          setUserPasswordStatus queue, username, 'valid'
-
-        ->
+            next()
+
+        (next) ->
+          setUserPasswordStatus username, 'valid', next
+
+        (next) ->
           # expecting successful login
           JUser.login null, loginCredentials, (err) ->
             expect(err).to.not.exist
-            queue.next()
-
-        ->
-          setUserPasswordStatus queue, username, 'needs reset'
-
-        ->
+            next()
+
+        (next) ->
+          setUserPasswordStatus username, 'needs reset', next
+
+        (next) ->
           # expecting unsuccessful login attempt
           JUser.login null, loginCredentials, (err) ->
             expect(err).to.exist
-            queue.next()
-
-        ->
-          setUserPasswordStatus queue, username, 'valid'
-
-        -> done()
+            next()
+
+        (next) ->
+          setUserPasswordStatus username, 'valid', next

       ]

-      daisy queue
+      async.series queue, done

     it 'should create a new session if clientId is not specified', (done) ->
@@ -478,13 +462,13 @@

       addRemoveUserLogsToQueue = (queue, username) ->
-        queue.push ->
+        queue.push (next) ->
           JLog.remove { username }, (err) ->
             expect(err).to.not.exist
-            queue.next()
+            next()

       addLoginTrialToQueue = (queue, tryCount) ->
-        queue.push ->
+        queue.push (next) ->
           JUser.login null, loginCredentials, (err) ->
             expectedError = switch
               when tryCount < JLog.tryLimit()
@@ -494,13 +478,13 @@
                 #{JLog.timeLimit()} minutes."

             expect(err.message).to.be.equal expectedError
-            queue.next()
-
-      queue.push ->
+            next()
+
+      queue.push (next) ->
         # creating a new user with the newly generated userinfo
         JUser.createUser userInfo, (err, user) ->
           expect(err).to.not.exist
-          queue.next()
+          next()

       # removing logs for a fresh start
       addRemoveUserLogsToQueue queue, loginCredentials.username
@@ -512,9 +496,7 @@
       # removing logs for this username after test passes
       addRemoveUserLogsToQueue queue, loginCredentials.username

-      queue.push -> done()
-
-      daisy queue
+      async.series queue, done

     it 'should pass error if invitationToken is set but not valid', (done) ->
@@ -572,15 +554,12 @@
         for username in reservedUsernames
           userFormData.username = username

-          queue.push ->
+          queue.push (next) ->
             JUser.convert client, userFormData, (err) ->
               expect(err.message).to.exist
-              queue.next()
-
-        # done callback will be called after all usernames checked
-        queue.push -> done()
-
-        daisy queue
+              next()
+
+        async.series queue, done

     it 'should pass error if passwords do not match', (done) ->
@@ -603,24 +582,22 @@

         queue = [

-          ->
+          (next) ->
             JUser.convert client, userFormData, (err) ->
               expect(err).to.not.exist
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             # sending a different email address, username will remain same(duplicate)
             userFormData.email = generateRandomEmail()

             JUser.convert client, userFormData, (err) ->
               expect(err.message).to.be.equal 'Errors were encountered during validation'
-              queue.next()
-
-          -> done()
-
-        ]
-
-        daisy queue
+              next()
+
+        ]
+
+        async.series queue, done

     it 'should pass error if email is in use', (done) ->
@@ -630,24 +607,22 @@

         queue = [

-          ->
+          (next) ->
             JUser.convert client, userFormData, (err) ->
               expect(err).to.not.exist
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             # sending a different username, email address will remain same(duplicate)
             userFormData.username = generateRandomUsername()

             JUser.convert client, userFormData, (err) ->
               expect(err?.message).to.be.equal 'Email is already in use!'
-              queue.next()
-
-          -> done()
-
-        ]
-
-        daisy queue
+              next()
+
+        ]
+
+        async.series queue, done

     describe 'when user data is valid', ->
@@ -656,33 +631,31 @@

         queue = [

-          ->
+          (next) ->
             JUser.convert client, userFormData, (err) ->
               expect(err).to.not.exist
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             params = { username : userFormData.username }

             JUser.one params, (err, { data : { email, registeredFrom } }) ->
               expect(err).to.not.exist
               expect(email).to.be.equal userFormData.email
               expect(registeredFrom.ip).to.be.equal client.clientIP
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             params = { 'profile.nickname' : userFormData.username }

             JAccount.one params, (err, { data : { profile } }) ->
               expect(err).to.not.exist
               expect(profile.nickname).to.be.equal userFormData.username
-              queue.next()
-
-          -> callback()
-
-        ]
-
-        daisy queue
+              next()
+
+        ]
+
+        async.series queue, callback

       it 'should be able to register user with lower case username', (done) ->
@@ -711,23 +684,21 @@

         queue = [

-          ->
+          (next) ->
             generateDummyClient { group : 'koding' }, (err, client_) ->
               expect(err).to.not.exist
               client = client_
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             # username won't matter, only client's account will be checked
             JUser.unregister client, generateRandomUsername(), (err) ->
               expect(err?.message).to.be.equal 'You are not registered!'
-              queue.next()
-
-          -> done()
-
-        ]
-
-        daisy queue
+              next()
+
+        ]
+
+        async.series queue, done

     describe 'when user is registered', ->
@@ -739,13 +710,13 @@

         queue = [

-          ->
+          (next) ->
             generateDummyClient { group : 'koding' }, (err, client_) ->
               expect(err).to.not.exist
               client = client_
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             JUser.convert client, userFormData, (err, data) ->
               expect(err).to.not.exist

@@ -753,19 +724,17 @@
               { account, newToken }      = data
               client.sessionToken        = newToken
               client.connection.delegate = account
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             # username won't matter, only client's account will be checked
             JUser.unregister client, generateRandomUsername(), (err) ->
               expect(err?.message).to.be.equal 'You must confirm this action!'
-              queue.next()
-
-          -> done()
-
-        ]
-
-        daisy queue
+              next()
+
+        ]
+
+        async.series queue, done

       it 'user should be updated if requester has the right to unregister', (done) ->
@@ -776,13 +745,13 @@

         queue = [

-          ->
+          (next) ->
             generateDummyClient { group : 'koding' }, (err, client_) ->
               expect(err).to.not.exist
               client = client_
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             # registering user
             JUser.convert client, userFormData, (err, data) ->
               expect(err).to.not.exist
@@ -791,64 +760,63 @@
               { account, newToken }      = data
               client.sessionToken        = newToken
               client.connection.delegate = account
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             # expecting user to exist before unregister
             JUser.one { username }, (err, user) ->
               expect(err)           .to.not.exist
               expect(user)          .to.exist
               expect(user.email)    .to.be.equal email
               expect(user.username) .to.be.equal username
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             # expecting name to exist before unregister
             JName.one { name : username }, (err, name) ->
               expect(err)  .to.not.exist
               expect(name) .to.exist
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             # expecting user account to exist before unregister
             JAccount.one { 'profile.nickname' : username }, (err, account) ->
               expect(err)                  .to.not.exist
               expect(account)              .to.exist
               expect(account.type)         .to.be.equal 'registered'
               expect(account.onlineStatus) .to.be.equal 'online'
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             # expecting successful unregister
             JUser.unregister client, username, (err) ->
               expect(err).to.not.exist
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             # expecting user to be deleted after unregister
             JUser.one { username }, (err, user) ->
               expect(err)   .to.not.exist
               expect(user)  .to.not.exist
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             # expecting name to be deleted
             JName.one { name : username }, (err, name) ->
               expect(err)  .to.not.exist
               expect(name) .to.not.exist
-              queue.next()
-          ->
+              next()
+
+          (next) ->
             # expecting user account to be deleted after unregister
             JAccount.one { 'profile.nickname' : username }, (err, account) ->
               expect(err)     .to.not.exist
               expect(account) .to.not.exist
-              queue.next()
-
-          -> done()
-
-        ]
-
-        daisy queue
+              next()
+
+        ]
+
+        async.series queue, done

   describe '#verifyRecaptcha()', ->
@@ -917,29 +885,27 @@

         queue = [

-          ->
+          (next) ->
             generateDummyClient { gorup : 'koding' }, (err, client_) ->
               expect(err).to.not.exist
               { sessionToken } = client_
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             selector = { clientId : sessionToken }
             modifier = { $unset : { username : 1 } }
             JSession.update selector, modifier, (err) ->
               expect(err).to.not.exist
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             JUser.authenticateClient sessionToken, (err, data) ->
               expect(err?.message).to.be.equal 'no username found'
-              queue.next()
-
-          -> done()
-
-        ]
-
-        daisy queue
+              next()
+
+        ]
+
+        async.series queue, done

       it 'should be handle guest user if username starts with guest-', (done) ->
@@ -949,33 +915,31 @@

         queue = [

-          ->
+          (next) ->
             generateDummyClient { group : 'koding' }, (err, client_) ->
               expect(err).to.not.exist
               { sessionToken } = client_
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             selector = { clientId : sessionToken }
             modifier = { $set : { username : guestUsername } }
             JSession.update selector, modifier, (err) ->
               expect(err).to.not.exist
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             JUser.authenticateClient sessionToken, (err, data) ->
               expect(err).to.not.exist
               expect(data.session).to.be.an 'object'
               expect(data.account).to.be.an 'object'
               expect(data.account.profile.nickname).to.be.equal guestUsername
               expect(data.account.type).to.be.equal 'unregistered'
-              queue.next()
-
-          -> done()
-
-        ]
-
-        daisy queue
+              next()
+
+        ]
+
+        async.series queue, done

       it 'should return error if username doesnt match any user', (done) ->
@@ -985,30 +949,28 @@

         queue = [

-          ->
+          (next) ->
             generateDummyClient { gorup : 'koding' }, (err, client_) ->
               expect(err).to.not.exist
               { sessionToken } = client_
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             selector = { clientId : sessionToken }
             modifier = { $set : { username : invalidUsername } }
             JSession.update selector, modifier, (err) ->
               expect(err).to.not.exist
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             JUser.authenticateClient sessionToken, (err, data) ->
               expect(err?.message).to.be.equal "no user found with
                                   #{invalidUsername} and sessionId"
-              queue.next()
-
-          -> done()
-
-        ]
-
-        daisy queue
+              next()
+
+        ]
+
+        async.series queue, done

       it 'should return session and account if session is valid', (done) ->
@@ -1017,13 +979,13 @@

         queue = [

-          ->
+          (next) ->
             generateDummyClient { gorup : 'koding' }, (err, client_) ->
               expect(err).to.not.exist
               { sessionToken } = client_
-              queue.next()
-
-          ->
+              next()
+
+          (next) ->
             JUser.authenticateClient sessionToken, (err, data) ->
               expect(err).to.not.exist
               expect(data.session).to.be.an 'object'
@@ -1032,13 +994,11 @@
               expect(data.account).to.be.an 'object'
               expect(data.account).to.have.property 'socialApiId'
               expect(data.account).to.have.property 'profile'
-              queue.next()
-
-          -> done()
-
-        ]
-
-        daisy queue
+              next()
+
+        ]
+
+        async.series queue, done

 beforeTests()


debug  blankEvent.endTime for editing existing events

--- src/app/events/eventDetail.controller.coffee
+++ src/app/events/eventDetail.controller.coffee
@@ -360,7 +360,7 @@
           when: # initialize values
             startDate: blankEvent.startTime
             startTime: blankEvent.startTime
-            endTime: blankEvent.startTime
+            endTime: moment(blankEvent.startTime).add(blankEvent.duration,'millisecond').toDate()
             asString: moment(blankEvent.startTime).format('ddd, MMM Do YYYY, h:mm a')
           updateWhen: ()->
             newV = modalModel.when


webapp: catching Edge error 800a025e -- fixes #1803

--- src/smc-webapp/r_misc.cjsx
+++ src/smc-webapp/r_misc.cjsx
@@ -559,7 +559,10 @@

     componentDidMount: ->
         if @props.autoSelect
-            ReactDOM.findDOMNode(@refs.input).select()
+            try
+                ReactDOM.findDOMNode(@refs.input).select()
+            catch e
+                # Edge sometimes complains about 'Could not complete the operation due to error 800a025e'

     clear_and_focus_search_input: ->
         @set_value('')


Convert all text in Battle to canned text.

--- server/bw/battle.coffee
+++ server/bw/battle.coffee
@@ -261,18 +261,18 @@

   # Passing -1 to turns makes the weather last forever.
   setWeather: (weatherName, turns=-1) ->
-    message = switch weatherName
-      when Weather.SUN  then "The sunlight turned harsh!"
-      when Weather.RAIN then "It started to rain!"
-      when Weather.SAND then "A sandstorm kicked up!"
-      when Weather.HAIL then "It started to hail!"
+    cannedText = switch weatherName
+      when Weather.SUN  then "SUN_START"
+      when Weather.RAIN then "RAIN_START"
+      when Weather.SAND then "SAND_START"
+      when Weather.HAIL then "HAIL_START"
       else
         switch @weather
-          when Weather.SUN  then "The sunlight faded."
-          when Weather.RAIN then "The rain stopped."
-          when Weather.SAND then "The sandstorm subsided."
-          when Weather.HAIL then "The hail stopped."
-    @message(message)  if message
+          when Weather.SUN  then "SUN_END"
+          when Weather.RAIN then "RAIN_END"
+          when Weather.SAND then "SAND_END"
+          when Weather.HAIL then "HAIL_END"
+    @cannedText(cannedText)  if cannedText
     @weather = weatherName
     @weatherDuration = turns
     pokemon.informWeather(@weather)  for pokemon in @getActiveAlivePokemon()
@@ -283,12 +283,10 @@
     weather = (if @hasWeatherCancelAbilityOnField() then Weather.NONE else @weather)
     weatherName == weather

-  weatherMessage: ->
+  weatherCannedText: ->
     switch @weather
-      when Weather.SAND
-        "The sandstorm rages."
-      when Weather.HAIL
-        "The hail crashes down."
+      when Weather.SAND then "SAND_CONTINUE"
+      when Weather.HAIL then "HAIL_CONTINUE"

   weatherUpkeep: ->
     if @weatherDuration == 1
@@ -296,8 +294,8 @@
     else if @weatherDuration > 1
       @weatherDuration--

-    message = @weatherMessage()
-    @message(message)  if message?
+    cannedText = @weatherCannedText()
+    @cannedText(cannedText)  if cannedText?

     activePokemon = @getActivePokemon().filter((p) -> !p.isFainted())
     for pokemon in activePokemon
@@ -305,10 +303,10 @@
       damage = pokemon.stat('hp') >> 4
       if @hasWeather(Weather.HAIL)
         if pokemon.damage(damage)
-          @message "#{pokemon.name} is buffeted by the hail!"
+          @cannedText('HAIL_HURT', pokemon)
       else if @hasWeather(Weather.SAND)
         if pokemon.damage(damage)
-          @message "#{pokemon.name} is buffeted by the sandstorm!"
+          @cannedText('SAND_HURT', pokemon)

   hasWeatherCancelAbilityOnField: ->
     _.any @getActivePokemon(), (pokemon) ->
@@ -671,12 +669,12 @@
   performMove: (pokemon, move) ->
     targets = @getTargets(move, pokemon)

-    @message "#{pokemon.name} has no moves left!"  if move == @struggleMove
+    @cannedText('NO_MOVES_LEFT', pokemon)  if move == @struggleMove

     if pokemon.pp(move) <= 0
       # TODO: Send move id instead
       pokemon.tell(Protocol.MAKE_MOVE, move.name)
-      @message "But there was no PP left for the move!"
+      @cannedText('NO_PP_LEFT')
       # TODO: Is this the right place...?
       pokemon.resetRecords()
     else


Mechanics: Don't display weather damage if no damage done.

--- server/bw/battle.coffee
+++ server/bw/battle.coffee
@@ -303,11 +303,11 @@
       continue  if pokemon.isWeatherDamageImmune(@weather)
       damage = pokemon.stat('hp') >> 4
       if @hasWeather(Weather.HAIL)
-        @message "#{pokemon.name} is buffeted by the hail!"
-        pokemon.damage(damage)
+        if pokemon.damage(damage)
+          @message "#{pokemon.name} is buffeted by the hail!"
       else if @hasWeather(Weather.SAND)
-        @message "#{pokemon.name} is buffeted by the sandstorm!"
-        pokemon.damage(damage)
+        if pokemon.damage(damage)
+          @message "#{pokemon.name} is buffeted by the sandstorm!"

   hasWeatherCancelAbilityOnField: ->
     _.any @getActivePokemon(), (pokemon) ->


Separate execution into query and load stages

--- server/execution.coffee
+++ server/execution.coffee
@@ -16,11 +16,12 @@
   if not query.string
     share.Queries.update(query._id, {$set: {isStale: false}})
     return
+  config = share.Configs.findOne({}, {transform: share.Transformations.config})
   user = Meteor.users.findOne(query.ownerId)
   numRecs = user.profile.numRecs
   callback = (result, error, code) ->
     share.Queries.update(query._id, {$set: {result: result, error: error, code: code, isStale: false}})
-  executeQuery(query, numRecs, false, callback)
+  loadQueryResult(query, config, numRecs, callback)

 Meteor.methods
   checkConnection: ->
@@ -31,6 +32,7 @@
       cmd: "--protocol=0-255"
       isQuick: true
     })
+    config = share.Configs.findOne({}, {transform: share.Transformations.config})
     query = share.Queries.findOne(queryId)
     query.string = share.buildQueryString(query) # after defaults are applied
     @unblock()
@@ -40,14 +42,14 @@
         fut.throw(new Meteor.Error(500, error))
       else
         fut.return(result)
-    query.startRecNum = 1
-    executeQuery(query, 1, false, callback)
+    executeQuery(query, config, callback)
     fut.wait()
     # quick queries are cleaned up automatically
   loadDataForCSV: (queryId) ->
     check(queryId, Match.App.QueryId)
     unless @userId
       throw new Match.Error("Operation not allowed for unauthorized users")
+    config = share.Configs.findOne({}, {transform: share.Transformations.config})
     query = share.Queries.findOne(queryId)
     unless @userId is query.ownerId
       throw new Match.Error("Operation not allowed for non-owners")
@@ -59,29 +61,40 @@
       else
         fut.return(result)
     query.startRecNum = 1
-    executeQuery(query, 0, false, callback)
+    loadQueryResult(query, config, 0, callback)
     fut.wait()
   getRwfToken: (queryId) ->
     check(queryId, Match.App.QueryId)
     unless @userId
       throw new Match.Error("Operation not allowed for unauthorized users")
+    config = share.Configs.findOne({}, {transform: share.Transformations.config})
     query = share.Queries.findOne(queryId)
     unless @userId is query.ownerId
       throw new Match.Error("Operation not allowed for non-owners")
     @unblock()
+    token = Random.id()
     fut = new Future()
-    callback = (result, error, code, token) ->
+    callback = (result, error, code) ->
       if error
         fut.throw(new Error(error))
       else
-        fut.return(token)
-    query.startRecNum = 1
-    executeQuery(query, 0, true, callback)
+        if config.isSSH
+          copyCommand = "scp " + config.getSSHOptions() + " -P " + config.port + " " + config.user + "@" + config.host + ":/tmp/" + query._id + ".rwf /tmp/" + token + ".rwf"
+        else
+          copyCommand = "cp /tmp/" + query._id + ".rwf /tmp/" + token + ".rwf"
+        Process.exec(copyCommand, Meteor.bindEnvironment((err, stdout, stderr) ->
+          result = stdout.trim()
+          error = stderr.trim()
+          code = if err then err.code else 0
+          if error
+            fut.throw(new Error(error))
+          else
+            fut.return(token)
+        ))
+    executeQuery(query, config, callback)
     fut.wait()

-executeQuery = (query, numRecs, binary, callback) ->
-  config = share.Configs.findOne({}, {transform: share.Transformations.config})
-  token = Random.id()
+executeQuery = (query, config, callback) ->
   rwsetbuildErrors = []
   rwsetbuildFutures = []
   _.each(["dipSet", "sipSet", "anySet"], (field) ->
@@ -151,53 +164,48 @@
   )
   Future.wait(rwsetbuildFutures)
   if rwsetbuildErrors.length
-    callback("", rwsetbuildErrors.join("\n"), 255, token)
+    callback("", rwsetbuildErrors.join("\n"), 255)
     return
   rwfilterArguments = query.string.split(" ")
-  if binary
-    rwfilterArguments.push("--pass=/tmp/" + token + ".rwf")
-  else
-    rwfilterArguments.push("--pass=stdout")
   if config.siteConfigFile
     rwfilterArguments.push("--site-config-file=" + config.siteConfigFile)
   if config.dataRootdir
     rwfilterArguments.push("--data-rootdir=" + config.dataRootdir)
+  rwfilterArguments.push("--pass=stdout > /tmp/" + query._id + ".rwf")
   command = "rwfilter " + rwfilterArguments.join(" ")
-  if not binary
+  if config.isSSH
+    command = config.wrapCommand(command)
+  Process.exec(command, Meteor.bindEnvironment((err, stdout, stderr) ->
+    result = stdout.trim()
+    error = stderr.trim()
+    code = if err then err.code else 0
+    callback(result, error, code)
+  ))
+
+loadQueryResult = (query, config, numRecs, callback) ->
+  executeQuery(query, config, Meteor.bindEnvironment((result, error, code) ->
+    commands = []
     if query.sortField
       rwsortArguments = ["--fields=" + query.sortField]
       if query.sortReverse
         rwsortArguments.push("--reverse")
       if config.siteConfigFile
         rwsortArguments.push("--site-config-file=" + config.siteConfigFile)
-      command += " | " + "rwsort " + rwsortArguments.join(" ")
+      commands.push("rwsort " + rwsortArguments.join(" "))
     rwcutArguments = ["--num-recs=" + numRecs, "--start-rec-num=" + query.startRecNum, "--delimited"]
     if query.fields.length
       rwcutArguments.push("--fields=" + _.intersection(query.fieldsOrder, query.fields).join(","))
     if config.siteConfigFile
       rwcutArguments.push("--site-config-file=" + config.siteConfigFile)
-    command += " | " + "rwcut " + rwcutArguments.join(" ")
-  if config.isSSH
-    command = config.wrapCommand(command)
-  Process.exec(command, Meteor.bindEnvironment((err, stdout, stderr) ->
-    result = stdout.trim()
-    error = stderr.trim()
-    code = if err then err.code else 0
-    if error is "rwfilter: Error writing to stream 'stdout': Broken pipe"
-      # rwcut closes stdin after reading first --num-recs records
-      error = ""
-      code = 0
-    if binary
-      if config.isSSH
-        scp = "scp " + config.getSSHOptions() + " -P " + config.port + " " + config.user + "@" + config.host + ":/tmp/" + token + ".rwf /tmp/" + token + ".rwf"
-        Process.exec(scp, Meteor.bindEnvironment((err, stdout, stderr) ->
-          result = stdout.trim()
-          error = stderr.trim()
-          code = if err then err.code else 0
-          callback(result, error, code, token)
-        ))
-      else
-        callback(result, error, code, token)
-    else
-      callback(result, error, code, token)
+    commands.push("rwcut " + rwcutArguments.join(" "))
+    commands[0] += " /tmp/" + query._id + ".rwf"
+    command = commands.join(" | ")
+    if config.isSSH
+      command = config.wrapCommand(command)
+    Process.exec(command, Meteor.bindEnvironment((err, stdout, stderr) ->
+      result = stdout.trim()
+      error = stderr.trim()
+      code = if err then err.code else 0
+      callback(result, error, code)
+    ))
   ))


Remove unnecessary data parameters

Remove unnecessary data parameters

--- annotran/static/scripts/annotator/plugin/sentenceselection.coffee
+++ annotran/static/scripts/annotator/plugin/sentenceselection.coffee
@@ -53,8 +53,6 @@
       endOffset: desiredText.length
       commonAncestor: document
       commonAncestorContainer: document
-      startContainer: event.target
-      endContainer: event.target
     }

     anchor = new xpathRange.SerializedRange(data).normalize(document)


Added mixpanel tracking for reset password actions

--- app/assets/javascript/fancy_login/forgot_password_fancybox.coffee
+++ app/assets/javascript/fancy_login/forgot_password_fancybox.coffee
@@ -29,6 +29,7 @@
     $('.forgot-pass').on "click", (e)=>
       @clear()
       $("#forgotpassbox input").val $("#login_form .email input").val()
+      mixingpanel_tracker.track "Login", {"action":"reset password click", "location":"login fancybox", "url": document.URL }
       e.preventDefault();

     $('.js-close').click (e)->
@@ -73,6 +74,8 @@
       form.find("button").attr('disabled', 'disabled')
       window.LoginBox.block()

+      mixingpanel_tracker.track "Login", {"action":"reset password attemp", "location":"login fancybox", "url": document.URL }
+
       $.ajax({
         url: form.data("url"),
         dataType: "JSONP",


Tickets once fetched, will be saved to window.tickets_col and this variable will be used for all other requests unless page refreshed.

--- app/assets/javascripts/backbone/views/home/tickets_view.js.coffee
+++ app/assets/javascripts/backbone/views/home/tickets_view.js.coffee
@@ -22,41 +22,14 @@
   render: ->
     that = @
     html = ""
+    if window.tickets_col
+      render_content window.tickets_col, that
+      return @
     @tickets = new Worksummarizer.Collections.TicketsCollection()
     @tickets.fetch {
       success: (col, res) ->
-
-        html = ""
-        unassigned = []
-        user_col = {}
-        $(".user_info").each (i) ->
-          user_col[$(this).attr('assembla_id')] = []
-
-        col.each (m) ->
-          if m.get('assigned_to_id') of user_col
-            user_col[m.get('assigned_to_id')].push m
-
-        html += "<input type='checkbox' class='cb_desc' /> Description"
-
-        for key, val of user_col
-          name_text = $("[assembla_id="+key+"]").text()
-          html += "

"+name_text+"

"
-          prev_space = ""
-          i = 0
-          for k,v of val
-            if prev_space != v.get('space_name')
-              if i!=0
-                html += "
"
-              html += "
"+v.get('space_name')+"
"
-              prev_space = v.get('space_name')
-
-            html += "
"+v.get('status')+" - "+v.get('summary')+"
"+v.get('description')+"

"
-            i++
-
-          html += "

"
-        $(that.el).html html
-        check_active_user_content()
-
+        window.tickets_col = col
+        render_content col, that

       error: (col, res) ->
@@ -65,8 +38,40 @@
     }
     return this

+  render_content= (col, that) ->
+    html = ""
+    unassigned = []
+    user_col = {}
+    $(".user_info").each (i) ->
+      user_col[$(this).attr('assembla_id')] = []

-check_active_user_content = (that)->
+    col.each (m) ->
+      if m.get('assigned_to_id') of user_col
+        user_col[m.get('assigned_to_id')].push m
+
+    html += "<input type='checkbox' class='cb_desc' /> Description"
+
+    for key, val of user_col
+      name_text = $("[assembla_id="+key+"]").text()
+      html += "

"+name_text+"

"
+      prev_space = ""
+      i = 0
+      for k,v of val
+        if prev_space != v.get('space_name')
+          if i!=0
+            html += "
"
+          html += "
"+v.get('space_name')+"
"
+          prev_space = v.get('space_name')
+
+        html += "
"+v.get('status')+" - "+v.get('summary')+"
"+v.get('description')+"

"
+        i++
+
+      html += "

"
+    $(that.el).html html
+    check_active_user_content()
+
+
+  check_active_user_content = (that)->
     console.log "ticket view function"
     if that
       $(that).toggleClass("active")


Ensure the current_path is updated as the page changes (when using turbolinks)

--- app/assets/javascripts/comfortable_mexican_sofa/cms/cms.js.coffee
+++ app/assets/javascripts/comfortable_mexican_sofa/cms/cms.js.coffee
@@ -1,9 +1,9 @@
 window.CMS ||= {}

-window.CMS.current_path           = window.location.pathname
-window.CMS.code_mirror_instances  = [ ]
+window.CMS.code_mirror_instances = [ ]

 $(document).on 'page:load ready', ->
+  window.CMS.current_path = window.location.pathname
   CMS.init()

 window.CMS.init = ->


增加 debug 选项

增加 debug 选项

--- app/assets/javascripts/file_part_upload/file_part_uploader.coffee
+++ app/assets/javascripts/file_part_upload/file_part_uploader.coffee
@@ -11,6 +11,8 @@

 class @QiniuFilePartUploader
   constructor: (options)->
+    @debug = options['debug']
+
     @$browse_button       = options['browse_button']
     @$drag_area_ele       = options['dragdrop_area']
     @max_file_size        = options['max_file_size'] || '100mb'
@@ -27,19 +29,23 @@

   init: ->
     dom_data = @$browse_button.data()
-    console.debug 'browse button dom data:'
-    console.debug dom_data
+
+    if @debug
+      console.debug 'browse button dom data:'
+      console.debug dom_data
+
     @qiniu_domain       = dom_data['qiniuDomain']
     @qiniu_base_path    = dom_data['qiniuBasePath']
     @qiniu_uptoken_url  = dom_data['qiniuUptokenUrl']
     @qiniu_callback_url = dom_data['qiniuCallbackUrl']

-    console.debug {
-      'qiniu_domain':       @qiniu_domain
-      'qiniu_base_path':    @qiniu_base_path
-      'qiniu_uptoken_url':  @qiniu_uptoken_url
-      'qiniu_callback_url': @qiniu_callback_url
-    }
+    if @debug
+      console.debug {
+        'qiniu_domain':       @qiniu_domain
+        'qiniu_base_path':    @qiniu_base_path
+        'qiniu_uptoken_url':  @qiniu_uptoken_url
+        'qiniu_callback_url': @qiniu_callback_url
+      }

     if not (@qiniu_domain and @qiniu_base_path and @qiniu_uptoken_url and @qiniu_callback_url)
       console.warn "需要在上传按钮的 DOM 上声明这些参数： data-qiniu-domain, data-qiniu-base-path, data-qiniu-uptoken-url, data-callback-url"
@@ -75,13 +81,15 @@

         # 该方法第二个被触发
         BeforeUpload: (up, file)=>
-          console.debug 'before upload'
+          if @debug
+            console.debug 'before upload'
           if not @file_progress_instances[file.id]?
             @file_progress_instances[file.id] = new @file_progress_class(file, up)

         # 该方法第三个被触发，上传结束前持续被触发
         UploadProgress: (up, file)=>
-          console.debug 'upload progress'
+          if @debug
+            console.debug 'upload progress'
           chunk_size = plupload.parseSize up.getOption('chunk_size')
           @file_progress_instances[file.id].update?()
           # 当前进度 #{file.percent}%，
@@ -89,7 +97,8 @@

         # 该方法在上传成功结束时触发
         FileUploaded: (up, file, info_json)=>
-          console.debug 'file uploaded, create file entity'
+          if @debug
+            console.debug 'file uploaded, create file entity'
           info = jQuery.parseJSON info_json
           # 这里写个回调钩子，虽然不是很常用
           @file_progress_instances[file.id].deal_file_entity?(info)


only draw the map once on startup

only draw the map once on startup

--- src/ui/canvas.coffee
+++ src/ui/canvas.coffee
@@ -83,14 +83,13 @@
         .attr('height', map.height)

     updateProjection(sc, tr)
-    map.centerAt(center, map.sc)
     zoom.scaleExtent([minScale, 3])

     actionbar.attr('transform', "translate(0, #{map.height - app.ACTIONBAR_HEIGHT})")
     actionbar.select('.background').attr('width', map.width)

+    map.centerAt(center, map.sc)
     resetMap()
-    map.dispatch.redraw()

   updateProjection = (new_sc, new_tr) ->
     map.sc = new_sc


fix when plans overlapped due to plans being returned out of order from BE, needed to sort by existing duration

--- src/components/course-calendar/duration.cjsx
+++ src/components/course-calendar/duration.cjsx
@@ -45,7 +45,9 @@
       # TODO these parts actually seem like they should be in flux
       .each(@setDuration(viewingDuration))
       .filter(@isInDuration(viewingDuration))
-      .sortBy('due_at')
+      .sortBy((plan) ->
+        -1 * plan.duration.start.valueOf()
+      )
       .value()

     groupedDurations = _.chain(groupingDurations)
@@ -184,7 +186,6 @@
     renderedDurations = _.map(@state.durationsByStartDate, @renderChildren)

   render: ->
-    {durations, viewingDuration, groupingDurations} = @props
     renderedDurations = @renderDurations()

     <div>


ScrollThumb: minor styling

ScrollThumb: minor styling

--- src/components/scrollview/scrollthumb.coffee
+++ src/components/scrollview/scrollthumb.coffee
@@ -30,7 +30,7 @@
     @scrollSize = null

-  handleMutation:->
+  handleMutation: ->

     @resetSizes()
     @calculateSize()


clean up

clean up

--- _src/coffee/directives/json-resource/JsonResource.coffee
+++ _src/coffee/directives/json-resource/JsonResource.coffee
@@ -1,92 +0,0 @@
-mapDataToURL = require "../../utils/MapDataToURL.coffee"
-_ = require "lodash"
-
-module.exports = ($timeout, $http) ->
-  restrict: 'E'
-#  scope:
-#    path: '@'
-#    variable: '@'
-#    quietMillis: '@'
-#    mapData: '@'
-#    queryDataType: '@'
-#    updateOnExpressionChange: '@'
-  link: (scope, elm, attrs) ->
-    timeoutPromise = null
-    minimumInputLength = if (attrs.minimumInputLength? && !isNaN(attrs.minimumInputLength)) then attrs.minimumInputLength else 3
-    maximumInputLength = if (attrs.maximumInputLength? && !isNaN(attrs.maximumInputLength)) then attrs.maximumInputLength else null
-    quietMillis = if (attrs.quietMillis? && !isNaN(attrs.quietMillis)) then attrs.quietMillis else 500
-    queryDataFilter = null
-    attrs.$observe("queryDataFilter", (newValue, oldValue) ->
-      queryDataFilter = newValue
-    )
-    attrs.$observe("updateOnExpressionChange", (newValue, oldValue) ->
-      return unless (newValue? && newValue != oldValue)
-      $timeout.cancel(timeoutPromise)
-      timeoutPromise = $timeout(
-        () ->
-          if (newValue.length >= minimumInputLength && (maximumInputLength == null || newValue.length < maximumInputLength))
-            load()
-      , quietMillis)
-    )
-    contains = (obj,a) ->
-      if _.isObject(obj)
-        r = false
-        _.forEach(obj, (value) ->
-          r = true if !_.isArray(value) && _.contains(a, value)
-        )
-        return r
-      else
-        _.contains(a, obj)
-
-
-    onSuccess = (response) ->
-      elm.parent().removeClass("ng-loading")
-      if attrs.variable?
-        if attrs.queryResultsArrayPath?
-          paths = attrs.queryResultsArrayPath.split(".")
-          a = response
-          _.forEach(paths, (path) ->
-            a = a[path]
-          )
-          if queryDataFilter?
-            a = _.remove(a, (item)->
-              return !contains(item, queryDataFilter)
-            )
-          scope[attrs.variable] = a
-        else
-          scope[attrs.variable] = response
-
-
-    onError = () ->
-      elm.parent().removeClass("ng-loading");
-      scope[attrs.variable] = {};
-
-    load = () ->
-      scope[attrs.variable] = [] unless attrs.variable?
-      scope[attrs.variable] = scope[attrs.variable] || []
-      url = mapDataToURL(attrs.path, attrs.mapData, scope)
-      if (!url? || url=="")
-        elm.parent().removeClass("ng-loading");
-        return
-      elm.parent().addClass("ng-loading");
-      dataType = attrs.queryDataType || "json"
-
-      if (dataType == "jsonp")
-        if (url.indexOf("?") < 0)
-          url += "?callback=JSON_CALLBACK"
-        else
-          if (url.indexOf("callback=JSON_CALLBACK") < 0)
-            url = url.replace(/\?/gi, "?callback=JSON_CALLBACK&")
-
-        $http.jsonp(url)
-          .success((response) ->
-            onSuccess(response)
-          )
-          .error(() ->
-            onError()
-          )
-      else
-        $http.get(url).success(onSuccess).error(onError)
-
-
-    load()


In IE7, it is not possible to get a character from a string using the box [] notation.
This caused IE8 to go return defined, enter a continious loop and then cause a stack overflow error.

Amended the offending lines to use charAt instead.

--- src/view/view_store.coffee
+++ src/view/view_store.coffee
@@ -16,7 +16,7 @@
   @accessor
     'final': true
     get: (path) ->
-      return @get("/#{path}") unless path[0] is '/'
+      return @get("/#{path}") unless path.charAt(0) is '/'
       return @_viewContents[path] if @_viewContents[path]
       return if @_requestedPaths.has(path)
       return contents if contents = @_sourceFromDOM(path)
@@ -26,7 +26,7 @@
         throw new Error("Couldn't find view source for \'#{path}\'!")
       return
     set: (path, content) ->
-      return @set("/#{path}", content) unless path[0] is '/'
+      return @set("/#{path}", content) unless path.charAt(0) is '/'
       @_requestedPaths.add(path)
       @_viewContents[path] = content


Analytic: minor styling

Analytic: minor styling

--- client/Main/analytic.coffee
+++ client/Main/analytic.coffee
@@ -1,5 +1,6 @@
 do->
-  KD.singleton('router').on "RouteInfoHandled", (argsForMixpanel)->
+
+  KD.singletons.router.on 'RouteInfoHandled', (argsForMixpanel) ->
     KD.gaPageView argsForMixpanel.path

     for own _ of argsForMixpanel.query


Changed event names to fit convention

Changed event names to fit convention

--- game.coffee
+++ game.coffee
@@ -36,7 +36,7 @@
     @inProgress = true
     @frameTimer = setInterval(@tick, @frameInterval)

-    @broadcast('startGame')
+    @broadcast('start-game')

   fetchFrameState: =>
     state =
@@ -93,7 +93,7 @@

   publishFrameState: =>
     @state = @fetchFrameState()
-    @broadcast('updateGameState', @state)
+    @broadcast('update-game-state', @state)

   tick: =>
     @updateFrameState()


Guard against undefined state in Workspace.deserialize

--- src/workspace.coffee
+++ src/workspace.coffee
@@ -29,6 +29,8 @@
   atom.deserializers.add(this)

   @deserialize: (state) ->
+    return unless state?
+
     for packageName in state.packagesWithActiveGrammars ? []
       atom.packages.getLoadedPackage(packageName)?.loadGrammarsSync()


Always check existing active for uri first

--- src/workspace.coffee
+++ src/workspace.coffee
@@ -97,11 +97,10 @@
     activatePane = options.activatePane ? options.changeFocus ? true
     uri = atom.project.relativize(uri) ? ''

-    if uri?
-      item = opener(atom.project.resolve(uri), options) for opener in @getOpeners() when !item
-      editor = item ? @activePane.itemForUri(uri) ? atom.project.openSync(uri, {initialLine})
-    else
-      editor = atom.project.openSync()
+    editor = @activePane.itemForUri(uri)
+    if uri
+      editor ?= opener(atom.project.resolve(uri), options) for opener in @getOpeners() when !editor
+    editor ?= atom.project.openSync(uri, {initialLine})

     @activePane.activateItem(editor)
     @itemOpened(editor)
@@ -110,10 +109,13 @@

   openUriInPane: (uri, pane, options={}) ->
     changeFocus = options.changeFocus ? true
-    item = opener(atom.project.resolve(uri), options) for opener in @getOpeners() when !item
-    promise = Q(item ? pane.itemForUri(uri) ? atom.project.open(uri, options))
-
-    promise
+
+    editor = pane.itemForUri(uri)
+    if uri
+      editor ?= opener(atom.project.resolve(uri), options) for opener in @getOpeners() when !editor
+    editor ?= atom.project.open(uri, options)
+
+    Q(editor)
       .then (editor) =>
         if not pane
           pane = new Pane(items: [editor])


Resolve NAN values

Resolve NAN values

--- app/assets/javascripts/backend/purchase_orders.js.coffee
+++ app/assets/javascripts/backend/purchase_orders.js.coffee
@@ -12,7 +12,7 @@
         vatRate = $(item).find('.total-column label.vat-rate').text().split("%")[0]

         totalAmountExcludingTaxes += parseFloat(amountExcludingTaxes)
-        totalAmountIncludingTaxes += parseInt(amountExcludingTaxes * (1 + (vatRate / 100)))
+        totalAmountIncludingTaxes += parseInt(amountExcludingTaxes * (1 + (parseFloat(vatRate) / 100)))
         totalVatRate += parseFloat(parseFloat(totalAmountIncludingTaxes - totalAmountExcludingTaxes).toFixed(2))
         $('.nested-item-form').each (index, item) ->


use remote form for new category form.

use remote form for new category form.

--- app/assets/javascripts/dashboard/articles/edit.js.coffee
+++ app/assets/javascripts/dashboard/articles/edit.js.coffee
@@ -22,8 +22,15 @@
     $("#publish-button").on "click", (event) => @publishArticle(event)
     $("#draft-button").on "click", (event) => @draftArticle(event)
     $("#category-form").on "submit", (event) => @saveCategory(event)
-    $("#new-category-form").on "submit", (event) => @createCategory(event)
     $("#pick-up-button").on "click", (event) => @pickUpTopbar(event)
+
+    $("#new-category-form").on "ajax:success", (event, data) ->
+      $li = $("
")
+      $li.find("a").text(data.name).data "category-id", data.urlname
+      $("#category-form .dropdown-menu").prepend $li
+      $("#category-form .dropdown-toggle").text data.name
+      $("#article-category-id").val data.urlname
+      Dialog.hide "#new-category-modal"

     $("#category-form .dropdown").on "click", ".dropdown-menu li a", (event) ->
       event.preventDefault()
@@ -118,21 +125,6 @@
       @article.data "category-id", categoryId
       @createArticle()
       Dialog.hide "#select-category-modal"
-
-  createCategory: (event) ->
-    event.preventDefault()
-    $.ajax(
-      url: "/~#{@space}/categories/"
-      data: $("#new-category-form").serializeArray()
-      type: "post"
-      dataType: "json"
-    ).success (data) ->
-      $li = $("
")
-      $li.find("a").text(data.name).data "category-id", data.urlname
-      $("#category-form .dropdown-menu").prepend $li
-      $("#category-form .dropdown-toggle").text data.name
-      $("#article-category-id").val data.urlname
-      Dialog.hide "#new-category-modal"

   saveArticle: (event) ->
     event.preventDefault() if event


do not start uploading if uploading is already cancelled

--- app/assets/javascripts/uploadcare/files/object.js.coffee
+++ app/assets/javascripts/uploadcare/files/object.js.coffee
@@ -71,19 +71,22 @@
       df = $.Deferred()

       if not @__file
+        @__rejectApi('baddata')
         return df
       if @fileSize > 100 * 1024 * 1024
         @__rejectApi('size')
         return df

-      formData = new FormData()
-      formData.append('UPLOADCARE_PUB_KEY', @settings.publicKey)
-      formData.append('UPLOADCARE_STORE', if @settings.doNotStore then '' else 'auto')
-      formData.append('file', @__file, @fileName)
-      formData.append('file_name', @fileName)
-
       @directRunner (release) =>
         df.always(release)
+        if @apiDeferred.state() != 'pending'
+          return
+
+        formData = new FormData()
+        formData.append('UPLOADCARE_PUB_KEY', @settings.publicKey)
+        formData.append('UPLOADCARE_STORE', if @settings.doNotStore then '' else 'auto')
+        formData.append('file', @__file, @fileName)
+        formData.append('file_name', @fileName)

         @__autoAbort($.ajax(
           xhr: =>


LMS-873: Discussion dates are not saved when the user attached a file

--- app/coffeescripts/views/DiscussionTopics/EditView.coffee
+++ app/coffeescripts/views/DiscussionTopics/EditView.coffee
@@ -222,12 +222,16 @@
         # so we can't make it undefined here for the case of discussion topics.
         data.assignment = @model.createAssignment(set_assignment: '0')

+      if !!data.attachment
+        data.delayed_post_at = Date.parse(data.delayed_post_at).toString() if data.delayed_post_at
+        data.lock_at = Date.parse(data.lock_at).toString() if data.lock_at
+
       # these options get passed to Backbone.sync in ValidatedFormView
       @saveOpts = multipart: !!data.attachment, proxyAttachment: true

       data

-    updateAssignment: (model_key, data) =>
+    updateAssignment: (model_key, data) =>
       @dueDateOverrideView.updateOverrides()
       defaultDate = @dueDateOverrideView.getDefaultDueDate()
       data.lock_at = defaultDate?.get('lock_at') or null


Stop using week.blocks

Stop using week.blocks

--- app/assets/javascripts/components/timeline/timeline.cjsx
+++ app/assets/javascripts/components/timeline/timeline.cjsx
@@ -69,7 +69,7 @@
           return

   _canBlockMoveDown: (week, weekIndexInTimeline, block, blockIndexInWeek) ->
-    return false if weekIndexInTimeline == @props.weeks.length - 1 && blockIndexInWeek == week.blocks.length - 1
+    return false if weekIndexInTimeline == @props.weeks.length - 1 && blockIndexInWeek == BlockStore.getBlocksInWeek(week.id).length - 1
     # TODO: return false if it's the last block in the last non-blackout week
     return true

@@ -121,7 +121,7 @@
       w.blocks.sort (a, b) ->
         a.order - b.order

-    @props.weeks.forEach (week) =>
+    @props.weeks.forEach (week, weekIndex) =>
       unless week.deleted
         if @props?.week_meetings
           while @props?.week_meetings[i] == '()'
@@ -163,8 +163,8 @@
               saveBlockChanges={@props.saveBlockChanges}
               cancelBlockEditable={@props.cancelBlockEditable}
               saveGlobalChanges={@props.saveGlobalChanges}
-              canBlockMoveUp={@_canBlockMoveUp.bind(this, week, i)}
-              canBlockMoveDown={@_canBlockMoveDown.bind(this, week, i)}
+              canBlockMoveUp={@_canBlockMoveUp.bind(this, week, weekIndex)}
+              canBlockMoveDown={@_canBlockMoveDown.bind(this, week, weekIndex)}
               onMoveBlockUp={@_handleMoveBlock.bind(this, true)}
               onMoveBlockDown={@_handleMoveBlock.bind(this, false)}
               onBlockDrag={@_handleBlockDrag}


resize+upload progress

resize+upload progress

--- app/assets/javascripts/uploadcare/files/object.js.coffee
+++ app/assets/javascripts/uploadcare/files/object.js.coffee
@@ -26,13 +26,22 @@
         @multipartUpload()
       else
         if @settings.imageReduce and utils.abilities.blob
-          op = utils.imageProcessor.reduceFile(@__file, @settings.imageReduce)
-          op.done (file) =>
-            @__file = file
-            @fileSize = @__file.size
-            @fileType = @__file.type or 'application/octet-stream'
-          op.always =>
-            @directUpload()
+          df = $.Deferred()
+
+          utils.imageProcessor.reduceFile(@__file, @settings.imageReduce)
+            .progress (progress) ->
+              df.notify(progress * .4)
+            .done (file) =>
+              @__file = file
+              @fileSize = @__file.size
+              @fileType = @__file.type or 'application/octet-stream'
+            .always =>
+              @directUpload()
+                .done(df.resolve)
+                .fail(df.reject)
+                .progress (progress) ->
+                  df.notify(.4 + progress * .6)
+          df
         else
           @directUpload()


zhTW fixes

zhTW fixes

--- app/assets/javascripts/uploadcare/locale/zh-TW.js.coffee
+++ app/assets/javascripts/uploadcare/locale/zh-TW.js.coffee
@@ -1,6 +1,6 @@
 # Note: English locale is the default and used as a fallback.
 uploadcare.namespace 'uploadcare.locale.translations', (ns) ->
-  ns.zh-TW =
+  ns.zhTW =
     uploading: '上傳中...請等待'
     loadingInfo: '正在讀取資訊...'
     errors:
@@ -15,7 +15,6 @@
       deleted: '檔案已被刪除'
     draghere: '拖放檔案到這裡'
     file:
-      one: '%1 個檔案'
       other: '%1 個檔案'
     buttons:
       cancel: '取消'
@@ -37,16 +36,6 @@
           file: '從電腦中選取檔案'
           url: '任意圖片連結'
           camera: '相機'
-          facebook: 'Facebook'
-          dropbox: 'Dropbox'
-          gdrive: 'Google Drive'
-          instagram: 'Instagram'
-          vk: 'VK'
-          evernote: 'Evernote'
-          box: 'Box'
-          skydrive: 'OneDrive'
-          flickr: 'Flickr'
-          huddle: 'Huddle'
         file:
           drag: '拖放檔案到這裡'
           nodrop: '從你的電腦中上傳'
@@ -68,7 +57,7 @@
             title: '請允許使存取您的相機'
             text: '你一直在提示允許來自這個網站的訪問攝像頭。' +
                   '為了拍照用你的相機，你必須批准這一請求。'
-		  notFound:
+          notFound:
             title: '沒有找到相機'
             text: '看起來你有沒有將連接相機。'
         preview:
@@ -114,12 +103,10 @@
             done: '完成'
       footer:
         text: '為您提供檔案上傳、存儲和編輯功能。 Copyright ©'
-        link: 'Uploadcare.com'

 # Pluralization rules taken from:
 # http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
 uploadcare.namespace 'uploadcare.locale.pluralize', (ns) ->
-  ns.zh-TW = (n) ->
-    return 'one' if n == 1
+  ns.zhTW = (n) ->
     'other'


just use plain req.ip for logging

just use plain req.ip for logging

--- services/web/app/coffee/infrastructure/ExpressLocals.coffee
+++ services/web/app/coffee/infrastructure/ExpressLocals.coffee
@@ -64,8 +64,7 @@
 		cdnBlocked = req.query.nocdn == 'true' or req.session.cdnBlocked

 		if cdnBlocked and !req.session.cdnBlocked?
-			ip = req.ip || req.socket?.socket?.remoteAddress || req.socket?.remoteAddress
-			logger.log user_id:req?.session?.user?._id, ip:ip, "cdnBlocked for user, not using it"
+			logger.log user_id:req?.session?.user?._id, ip:req?.ip, "cdnBlocked for user, not using it and turning it off for future requets"
 			req.session.cdnBlocked = true

 		isDark = req.headers?.host?.slice(0,4)?.toLowerCase() == "dark"


clean up old code

clean up old code

--- shared/idle/controllers/players/IdlePlayerController.coffee
+++ shared/idle/controllers/players/IdlePlayerController.coffee
@@ -209,8 +209,7 @@
             "
#{embedKey} (#{obj[key]})
"
           .join ""

-        return "
#{buildHtmlFromObject val, true}
" if (_.isObject val) and key is 'calculated kills'
-        return "
#{buildHtmlFromObject val, false}
" if (_.isObject val) and key isnt 'calculated kills'
+        return "
#{buildHtmlFromObject val, key is 'calculated kills'}
" if (_.isObject val)
         parseInt val

       $scope.getStats = (player) ->


no longer needed

no longer needed

--- site/coffee/site.coffee
+++ site/coffee/site.coffee
@@ -1,2 +0,0 @@
-$ ->
-  alert 'coffee is working'


remove commented out console log

remove commented out console log

--- coach/src/user/model.coffee
+++ coach/src/user/model.coffee
@@ -48,7 +48,7 @@

   status: (collectionUUID, enrollmentCode) ->
     course = @getCourse(collectionUUID, enrollmentCode)
-    # console.info('status course', course, enrollmentCode)
+
     isLoggedIn: @isLoggedIn()
     isLoaded:   @isLoaded
     isRegistered: !!course?.isRegistered()


Enable infix search in results

Enable infix search in results

--- coffee/storage_freak.coffee
+++ coffee/storage_freak.coffee
@@ -183,9 +183,7 @@
         query = query.replace re, "(?:#{value[0]}|#{value[1]})"

       re = null
-      return new RegExp "^#{query}", 'ig'
-
-
+      return new RegExp "#{query}", 'ig'

     ## REFACTOR THOSE
     ## MAYBE REMOVE THOSE??


Fix load area

Fix load area

--- assets/javascripts/react/components/load_selector.coffee
+++ assets/javascripts/react/components/load_selector.coffee
@@ -48,8 +48,10 @@
     React.render(modalInstance, @refs.loadContainer.getDOMNode())

   render: ->
-    <Button bsStyle="primary" id="loadButton" onClick={@showModal}>
+
+      <Button bsStyle="primary" id="loadButton" onClick={@showModal}>
+        Load
+      </Button>

-      Load
-    </Button>
+


Set state after stopping polling

Set state after stopping polling

--- SingularityUI/app/components/aggregateTail/Contents.cjsx
+++ SingularityUI/app/components/aggregateTail/Contents.cjsx
@@ -106,9 +106,9 @@
     $(@scrollNode).scrollTop(height);

   scrollToTop: ->
+    @stopTailingPoll()
     @setState
       isLoading: true
-    @stopTailingPoll()
     @props.fetchPrevious().done =>
       @setScrollHeight(0)
       @setState


website: fix for favicon in Firefox

website: fix for favicon in Firefox

--- website/scripts/main.coffee
+++ website/scripts/main.coffee
@@ -69,7 +69,6 @@
     drawRoundRect(ctx, x + border, y + border, w - border * 2, h - border * 2, r - border, fill)

   drawText = (ctx, text, x, y, color) ->
-    ctx.textBaseline = "middle"
     ctx.textAlign = "center"
     ctx.font = 'bold 9px sans-serif'
     ctx.fillStyle = color
@@ -91,7 +90,7 @@
     drawRectWithBorder(ctx, 0, 2, 16, 14, 2, 1, '#20aa9c', '#1b8e82')
     drawRoundRect(ctx, 2, 1, 3, 3, 1.5, '#1b8e82')
     drawRoundRect(ctx, 11, 1, 3, 3, 1.5, '#1b8e82')
-    drawText(ctx, moment().format('DD'), 8, 9.5, '#146960')
-    drawText(ctx, moment().format('DD'), 8, 8.5, '#fff')
+    drawText(ctx, moment().format('DD'), 8, 12.5, '#146960')
+    drawText(ctx, moment().format('DD'), 8, 11.5, '#fff')

     canvasToFavicon(canvas)


Syncing JSON Schema's list of basic tests with JSON's.

--- test/formats/formats-test.coffee
+++ test/formats/formats-test.coffee
@@ -84,6 +84,7 @@
     # 'object-very-complex'
     # 'one-of'
     # 'one-of-complex'
+    # 'one-of-properties'
     'optional'
     'required'
     # 'required-array'


Silence logs

Silence logs

--- components/SCDDetect.coffee
+++ components/SCDDetect.coffee
@@ -31,8 +31,6 @@
 onEnd = (tmpFile, cascade, callback) ->
   bin = path.join __dirname, '../build/Release/scddetect'
   exec "#{bin} #{tmpFile.path} #{cascade}", (err, stdout, stderr) ->
-    console.log 'stdout', stdout
-    console.log 'stderr', stderr
     if stderr
       callback stderr
       tmpFile.unlink()


Clean up test for escaped characters in command panel

--- src/extensions/command-panel/spec/command-panel-spec.coffee
+++ src/extensions/command-panel/spec/command-panel-spec.coffee
@@ -320,6 +320,16 @@

         expect(commandPanel).not.toHaveClass 'error'

+    describe "when the command contains an escaped charachter", ->
+      it "executes the command with the escaped character (instead of as a backslash followed by the character)", ->
+        rootView.trigger 'command-panel:toggle'
+
+        editSession = rootView.open(require.resolve 'fixtures/sample-with-tabs.coffee')
+        editor.edit(editSession)
+        commandPanel.miniEditor.setText "/\\tsell"
+        commandPanel.miniEditor.hiddenInput.trigger keydownEvent('enter')
+        expect(editor.getSelectedBufferRange()).toEqual [[3,1],[3,6]]
+
   describe "when move-up and move-down are triggerred on the editor", ->
     it "navigates forward and backward through the command history", ->
       commandPanel.execute 's/war/peace/g'
@@ -406,9 +416,4 @@
         editSession = rootView.getActiveEditSession()
         expect(editSession.buffer.getPath()).toBe project.resolve(operation.getPath())
         expect(editSession.getSelectedBufferRange()).toEqual operation.getBufferRange()
-        expect(rootView.focus).toHaveBeenCalled()
-
-  describe ".escapedCommand()", ->
-    it "escapes characters with a backslash in front of them", ->
-      commandPanel.miniEditor.setText("a\\tb")
-      expect(commandPanel.escapedCommand()).toBe("a\tb")
+        expect(rootView.focus).toHaveBeenCalled()


Quote data-attributes for points vote selector

--- plugins/points/assets/points/js/frontend.coffee
+++ plugins/points/assets/points/js/frontend.coffee
@@ -738,7 +738,7 @@
     @$el.effect('highlight', {}, 5000)

   flashSupporter: (data) =>
-    q = @$("[data-id=#{data.user_id or ""}][data-name=#{data.name or ""}]")
+    q = @$("[data-id=\"#{data.user_id or ""}\"][data-name=\"#{data.name or ""}\"]")
     q.addClass("added").effect('highlight', {}, 5000)

   startDrag: (event) =>


Fixed missing "" from config lines. I needed these to deploy on Heroku

--- src/scripts/github-issues.coffee
+++ src/scripts/github-issues.coffee
@@ -1,8 +1,8 @@
 # Show open issues from a Github repository.
 #
 # You need to set the following variables:
-#   HUBOT_GITHUB_TOKEN = <oauth token>
-#   HUBOT_GITHUB_USER = <user name>
+#   HUBOT_GITHUB_TOKEN ="<oauth token>"
+#   HUBOT_GITHUB_USER ="<user name>"
 #
 # HUBOT_GITHUB_USER is optional, but if you set it, you can ask `show me issues
 # for hubot` instead of `show me issues for github/hubot`.


Documentate.

Documentate.

--- src/scripts/pingdom-tools.coffee
+++ src/scripts/pingdom-tools.coffee
@@ -8,12 +8,8 @@
 #   none
 #
 # Commands:
-#   hubot pingdom checks - returns summary of all checks pingdom currently monitoring
-#   hubot pingdom alerts - returns list of most recent 10 alerts generated
-#   hubot start watching pingdom - starts hubot periodically checking pingdom to see if any checks are down
-#   hubot pingdom start - starts hubot periodically checking pingdom to see if any checks are down
-#   hubot stop watching pingdom - stops hubot periodically checking pingdom
-#   hubot pingdom stop - stops hubot periodically checking pingdom
+#   hubot is github.com (fast|slow)?
+#   hubot (pagetest|fpt) github.com

 class PingdomTools


screwed file system capitals

screwed file system capitals

--- src/main/webapp/lib/models/SplitratioProfile.coffee
+++ src/main/webapp/lib/models/SplitratioProfile.coffee
@@ -1,52 +0,0 @@
-class window.beats.SplitRatioProfile extends Backbone.Model
-  ### $a = alias for beats namespace ###
-  $a = window.beats
-  @from_xml1: (xml, object_with_id) ->
-    deferred = []
-    obj = @from_xml2(xml, deferred, object_with_id)
-    fn() for fn in deferred
-    obj
-
-  @from_xml2: (xml, deferred, object_with_id) ->
-    return null if (not xml? or xml.length == 0)
-    obj = new window.beats.SplitRatioProfile()
-    splitratio = xml.children('splitratio')
-    obj.set('splitratio', _.map($(splitratio), (splitratio_i) -> $a.Splitratio.from_xml2($(splitratio_i), deferred, object_with_id)))
-    crudFlag = $(xml).attr('crudFlag')
-    obj.set('crudFlag', crudFlag)
-    id = $(xml).attr('id')
-    obj.set('id', Number(id))
-    node_id = $(xml).attr('node_id')
-    obj.set('node_id', Number(node_id))
-    start_time = $(xml).attr('start_time')
-    obj.set('start_time', Number(start_time))
-    dt = $(xml).attr('dt')
-    obj.set('dt', Number(dt))
-    network_id = $(xml).attr('network_id')
-    obj.set('network_id', Number(network_id))
-    destination_network_id = $(xml).attr('destination_network_id')
-    obj.set('destination_network_id', Number(destination_network_id))
-    mod_stamp = $(xml).attr('mod_stamp')
-    obj.set('mod_stamp', mod_stamp)
-    if obj.resolve_references
-      obj.resolve_references(deferred, object_with_id)
-    obj
-
-  to_xml: (doc) ->
-    xml = doc.createElement('splitRatioProfile')
-    if @encode_references
-      @encode_references()
-    _.each(@get('splitratio') || [], (a_splitratio) -> xml.appendChild(a_splitratio.to_xml(doc)))
-    xml.setAttribute('crudFlag', @get('crudFlag')) if @has('crudFlag')
-    xml.setAttribute('id', @get('id')) if @has('id')
-    xml.setAttribute('node_id', @get('node_id')) if @has('node_id')
-    if @has('start_time') && @start_time != 0 then xml.setAttribute('start_time', @get('start_time'))
-    xml.setAttribute('dt', @get('dt')) if @has('dt')
-    xml.setAttribute('network_id', @get('network_id')) if @has('network_id')
-    xml.setAttribute('destination_network_id', @get('destination_network_id')) if @has('destination_network_id')
-    if @has('mod_stamp') && @mod_stamp != "0" then xml.setAttribute('mod_stamp', @get('mod_stamp'))
-    xml
-
-  deep_copy: -> SplitRatioProfile.from_xml1(@to_xml(), {})
-  inspect: (depth = 1, indent = false, orig_depth = -1) -> null
-  make_tree: -> null


StacksCustomViews: Define Stack and TestAndSave Step #3, #4

--- client/admin/lib/views/stacks/stackscustomviews.coffee
+++ client/admin/lib/views/stacks/stackscustomviews.coffee
@@ -102,6 +102,39 @@

       return container

+
+    stepDefineStack: (options) =>
+
+      console.log options
+      {callback, cancelCallback, data} = options
+      {provider} = data
+      container = @views.container 'step-creds'
+
+      views     = @addTo container,
+        stepsHeaderView : 3
+        navButton_prev  :
+          callback      : ->
+            cancelCallback {provider}
+        navButton_next  : {callback}
+
+      return container
+
+
+    stepTestAndSave: (options) =>
+
+      console.log options
+      {callback, cancelCallback} = options
+      container = @views.container 'step-creds'
+
+      views     = @addTo container,
+        stepsHeaderView : 4
+        navButton_prev  :
+          callback      : cancelCallback
+        navButton_next  : {callback}
+
+      return container
+
+
     providersView: (options) =>

       {providers} = options


Sidebar: Prepend VMs list.

Sidebar: Prepend VMs list.

--- client/app/lib/activity/sidebar/activitysidebar.coffee
+++ client/app/lib/activity/sidebar/activitysidebar.coffee
@@ -775,10 +775,12 @@
     @fetchMachines (machines) =>
       @machineLists = []
       @machineListsByName = {}
-      @addSubView @ownMachinesList = new SidebarOwnMachinesList {}, machines.own
+
+      @ownMachinesList = new SidebarOwnMachinesList {}, machines.own

       @machineLists.push @ownMachinesList
       @machineListsByName.own = @ownMachinesList
+      @addSubView @ownMachinesList, null, yes

   # handleMachineItemClick: (machineItem, event) ->


Deprecate setting channel id option of share popup

--- client/app/lib/navigation/navigationmachineitem.coffee
+++ client/app/lib/navigation/navigationmachineitem.coffee
@@ -170,9 +170,7 @@

   showSharePopup: (options = {}) ->

-    options.position  = @getPopupPosition 20
-    options.channelId = @getChannelId @getData()
-
+    options.position = @getPopupPosition 20
     new SidebarMachineSharePopup options, @machine


Cleanup whitespace

Cleanup whitespace

--- client/app/Applications/Topics.kdapplication/AppController.coffee
+++ client/app/Applications/Topics.kdapplication/AppController.coffee
@@ -1,5 +1,5 @@
 class Topics12345 extends AppController
-
+
   constructor:(options, data)->
     options = $.extend
       # view : if /localhost/.test(location.host) then new TopicsMainView cssClass : "content-page topics" else new TopicsComingSoon
@@ -8,26 +8,26 @@
     ,options
     super options,data
     @controllers = {}
-
+
   bringToFront:()->
     @propagateEvent (KDEventType : 'ApplicationWantsToBeShown', globalEvent : yes),
       options :
         name : 'Topics'
       data : @getView()
-
+
   initAndBringToFront:(options,callback)->
     @bringToFront()
     callback()
-
+
   createFeed:(view)->
     appManager.tell 'Feeder', 'createContentFeedController', {
       subItemClass          : TopicsListItemView
       limitPerPage          : 20
-      # feedMessage           :
+      # feedMessage           :
       #   title                 : "Topics organize shared content on Koding. Tag items when you share, and follow topics to see content relevant to you in your activity feed."
       #   messageLocation       : 'Topics'
       help                  :
-        subtitle            : "Learn About Topics"
+        subtitle            : "Learn About Topics"
         tooltip             :
           title             : "
Topic Tags organize content that users share on Koding. Follow the topics you are interested in and we'll include the tagged items in your activity feed.
"
           placement         : "above"
@@ -61,9 +61,9 @@
     mainView.createCommons()
     @createFeed mainView
     # mainView.on "AddATopicFormSubmitted",(formData)=> @addATopic formData
-
+
   fetchFeedForHomePage:(callback)->
-    options =
+    options =
       limit     : 6
       skip      : 0
       sort      :


DRY up

DRY up

--- services/web/public/coffee/ide/editor/Document.coffee
+++ services/web/public/coffee/ide/editor/Document.coffee
@@ -256,17 +256,7 @@
 					return callback(error) if error?
 					@joined = true
 					@doc.catchUp( updates )
-
-					if ranges?.changes
-						changes = for change in ranges.changes
-							change.op.i = decodeURIComponent(escape(change.op.i)) if change.op.i
-							change.op.d = decodeURIComponent(escape(change.op.d)) if change.op.d
-							change
-					if ranges?.comments
-						comments = for comment in ranges.comments
-							comment.op.c = decodeURIComponent(escape(comment.op.c))
-							comment
-
+					{ changes, comments } = @_decodeRanges(ranges)
 					@_catchUpRanges( changes, comments )
 					callback()
 			else
@@ -274,20 +264,22 @@
 					return callback(error) if error?
 					@joined = true
 					@doc = new ShareJsDoc @doc_id, docLines, version, @ide.socket
-
-					if ranges?.changes
-						changes = for change in ranges.changes
-							change.op.i = decodeURIComponent(escape(change.op.i)) if change.op.i
-							change.op.d = decodeURIComponent(escape(change.op.d)) if change.op.d
-							change
-					if ranges?.comments
-						comments = for comment in ranges.comments
-							comment.op.c = decodeURIComponent(escape(comment.op.c))
-							comment
-
+					{ changes, comments } = @_decodeRanges(ranges)
 					@ranges = new RangesTracker(changes, comments)
 					@_bindToShareJsDocEvents()
 					callback()
+
+		_decodeRanges: (ranges) ->
+			if ranges?.changes
+				changes = for change in ranges.changes
+					change.op.i = decodeURIComponent(escape(change.op.i)) if change.op.i
+					change.op.d = decodeURIComponent(escape(change.op.d)) if change.op.d
+					change
+			if ranges?.comments
+				comments = for comment in ranges.comments
+					comment.op.c = decodeURIComponent(escape(comment.op.c))
+					comment
+			{ changes, comments }

 		_leaveDoc: (callback = (error) ->) ->
 			sl_console.log '[_leaveDoc] Sending leaveDoc request'


cancel btn

cancel btn

--- app/scripts/editor.coffee
+++ app/scripts/editor.coffee
@@ -108,6 +108,10 @@
 			$('#insertion .toolbar').addClass 'clicked'
 			# self.hideInsertion()

+		$('#insertion').delegate '.btn-cancel', 'click', (e)->
+			$('#insertion .toolbar').removeClass 'clicked'
+			$('#insertion input').val ''
+
 	update: ->
 		self = @
 		# giving names


Give order property based on last question/form

--- app/server/methods.coffee
+++ app/server/methods.coffee
@@ -1,5 +1,6 @@
 getSurveys = => @Surveys
 getForms = => @Forms
+getQuestions = => @Questions

 Meteor.methods
   createSurvey: (fields)->
@@ -10,12 +11,18 @@

   createForm: (surveyId, props)->
     #TODO Authenticate
-    currentFormCount = getSurveys().findOne(surveyId).forms.length or 0
+    surveyForms = getSurveys().findOne(surveyId).forms
+    if surveyForms?.length
+      lastForm = getForms().findOne
+        _id: {$in: surveyForms}, {sort: order: -1}
+      order = ++lastForm.order
+    else
+      order = 1
     formId = getForms().insert
       name: props.name
       createdBy: @userId
       questions: []
-      order: ++currentFormCount
+      order: order
     getSurveys().update({_id: surveyId}, {$addToSet: {forms: formId}})
     formId

@@ -28,8 +35,13 @@

   addQuestion: (formId, data) ->
     form = Forms.findOne(formId)
-    currentQuestionCount = form.questions.length or 0
-    data.order = ++currentQuestionCount
+    formQuestions = form.questions
+    if formQuestions?.length
+      lastQuestion = getQuestions().findOne
+        _id: {$in: formQuestions}, {sort: order: -1}
+      data.order = ++lastQuestion.order
+    else
+      data.order = 1
     questionId = Questions.insert data
     if questionId
       form.questions.push questionId


Email bugfix

Email bugfix

--- src/server/controllers/applicationController.coffee
+++ src/server/controllers/applicationController.coffee
@@ -6,7 +6,9 @@

       sendgrid = require("sendgrid")(process.env.SENDGRID_USER, process.env.SENDGRID_PASS)

-      res.json { err: true } if req.body.email is undefined or req.body.message is undefined
+      if req.body.email is undefined or req.body.message is undefined
+        res.json { err: true }
+        return

       sendgrid.send {
         to: "sam@sampettersson.com",


Logging in with another user without logging out previously

--- app/modules/external-apps/external-app.controller.coffee
+++ app/modules/external-apps/external-app.controller.coffee
@@ -41,7 +41,7 @@
         @._application = null
         nextUrl = encodeURIComponent(@location.url())
         loginUrl = @navUrls.resolve("login")
-        @.loginWithAnotherUserUrl = "#{loginUrl}?next=#{nextUrl}"
+        @.loginWithAnotherUserUrl = "#{loginUrl}?next=#{nextUrl}&force_login=1"

         taiga.defineImmutableProperty @, "user", () => @._user
         taiga.defineImmutableProperty @, "application", () => @._application


Fix campaign index update from campaign editor (#5639)

--- app/views/editor/campaign/CampaignEditorView.coco.coffee
+++ app/views/editor/campaign/CampaignEditorView.coco.coffee
@@ -146,8 +146,6 @@
       # Save campaign to level if it's a main 'hero' campaign so HeroVictoryModal knows where to return.
       # (Not if it's a defaulted, typeless campaign like game-dev-hoc or auditions.)
       campaignLevel.campaign = @campaign.get 'slug' if @campaign.get('type') is 'hero' or @campaign.get('isOzaria')
-      # Save campaign index to level if it's a course campaign, since we show linear level order numbers for course levels.
-      campaignLevel.campaignIndex = (@levels.models.length - levelIndex - 1) if @campaign.get('type', true) is 'course'
       campaignLevels[levelOriginal] = campaignLevel

     @campaign.set('levels', campaignLevels)


bubble up error from user lookup

bubble up error from user lookup

--- apps/artwork_purchase/client/purchase_signup_form.coffee
+++ apps/artwork_purchase/client/purchase_signup_form.coffee
@@ -20,6 +20,9 @@
     @loggedOutUser.set (data = @serializeForm())

     userLookup = @loggedOutUser.findOrCreate silent: true
+      .catch ({ responseJSON }) =>
+        @showError responseJSON.message
+        error?()
       .then =>
         Q.promise (resolve) =>
           @loggedOutUser.related().account.fetch


fix #494

fix #494

fix for backbone function that was renamed

--- bokehjs/src/coffee/server/usercontext/usercontext.coffee
+++ bokehjs/src/coffee/server/usercontext/usercontext.coffee
@@ -149,7 +149,7 @@
       resp.done((data) =>
         docs = data['docs']
         if options.update
-          @update(docs, options)
+          @set(docs, options)
         else
           @reset(docs, options)
       )
@@ -159,4 +159,4 @@
   exports.UserDocsView = UserDocsView
   exports.Doc = Doc
   exports.DocView = DocView
-  return exports+  return exports


uncomment failing tests with some fixes

uncomment failing tests with some fixes

--- test/IntegrationSpec.coffee
+++ test/IntegrationSpec.coffee
@@ -121,70 +121,69 @@

           @it 'two', ->
             console.log 'in two'
-            console.log collection
-            # expect(third).toBeEqual 'Third'
-            # expect(collection).toBeEqual 'something else'
-            # expect(another).toBeEqual 'something else another'
-
-          # @it 'three', ->
-          #   console.log 'in three'
-            # expect(third).toBeEqual 'Third'
-            # expect(collection).toBeEqual 'something else'
-            # expect(another).toBeEqual 'something else another'
-
-      JE.run(context)
-
-    fit 'dependencies with inner describe part 2', ->
+            expect(third).toBeEqual 'Third'
+            expect(collection).toBeEqual 'something else'
+            expect(another).toBeEqual 'something else another'
+
+          @it 'three', ->
+            console.log 'in three'
+            expect(third).toBeEqual 'Third'
+            expect(collection).toBeEqual 'something else'
+            expect(another).toBeEqual 'something else another'
+
+      JE.run(context)
+
+    it 'dependencies with inner describe part 2', ->
       context.describe 'test4', ->
         another   .is collection + ' another'
         third     .is 'Third'

         @it 'one', ->
-          console.log 'in it'
           expect(third).toBeEqual 'Third'
-          expect(collection).toBeUndefined()
+          expect('collection').not.toBePresent()
           expect(another).toBeUndefined()
-          expect(fourth).toBeUndefined()
-          expect(fifth).toBeUndefined()
+          expect('fourth').not.toBePresent()
+          expect('fifth').not.toBePresent()

         @describe 'inner_first', ->
           collection.is 'something else one'
           fourth.is another + ' fourth'

           @it 'two', ->
-            console.log 'in inner it'
-            console.log another
+            console.log 'in two'
             expect(third).toBeEqual 'Third'
             expect(collection).toBeEqual 'something else one'
             expect(another).toBeEqual 'something else one another'
             expect(fourth).toBeEqual 'something else one another fourth'
-            expect(fifth).toBeUndefined()
+            expect('fifth').not.toBePresent()

           @it 'three', ->
+            console.log 'in three'
             expect(third).toBeEqual 'Third'
             expect(collection).toBeEqual 'something else one'
             expect(another).toBeEqual 'something else one another'
             expect(fourth).toBeEqual 'something else one another fourth'
-            expect(fifth).toBeUndefined()
+            expect('fifth').not.toBePresent()

         @describe 'inner_second', ->
           collection.is 'something else second'
           fifth.is another + ' fifth'

           @it 'fourth', ->
-            console.log another
+            console.log 'in fourth'
             expect(third).toBeEqual 'Third'
             expect(collection).toBeEqual 'something else second'
             expect(another).toBeEqual 'something else second another'
             expect(fifth).toBeEqual 'something else second another fifth'
-            expect(fourth).toBeUndefined()
+            expect('fourth').not.toBePresent()

           @it 'fifth', ->
+            console.log 'in fifth'
             expect(third).toBeEqual 'Third'
             expect(collection).toBeEqual 'something else second'
             expect(another).toBeEqual 'something else second another'
             expect(fifth).toBeEqual 'something else second another fifth'
-            expect(fourth).toBeUndefined()
+            expect('fourth').not.toBePresent()

       JE.run(context)


Move cached model stuff into a function

Move cached model stuff into a function

--- app/assets/javascripts/controllers/task_controller.js.coffee
+++ app/assets/javascripts/controllers/task_controller.js.coffee
@@ -10,6 +10,13 @@
   isEditable: Ember.computed.or('isUserEditable', 'isCurrentUserAdmin')

   redirectStack: Ember.computed.alias 'controllers.application.overlayRedirect'
+
+  clearCachedModel: (transition) ->
+    redirectStack = @get('redirectStack')
+    if !Em.isEmpty(redirectStack)
+      redirectRoute = redirectStack.popObject()
+      unless transition.targetName == redirectRoute.get('firstObject')
+        @get('controllers.application').set('cachedModel', null)

   actions:
     #saveModel is implemented in ETahi.SavesDelayed
@@ -42,8 +49,4 @@
           transition.abort()
           return

-      redirectStack = @get('redirectStack')
-      if !Em.isEmpty(redirectStack)
-        redirectRoute = redirectStack.popObject()
-        unless transition.targetName == redirectRoute.get('firstObject')
-          @get('controllers.application').set('cachedModel', null)
+      @clearCachedModel(transition)


copy uptime data

copy uptime data

--- app/assets/javascripts/helpers/average_uptime_builder.coffee
+++ app/assets/javascripts/helpers/average_uptime_builder.coffee
@@ -1,6 +1,6 @@
 class helpers.AverageUptimeBuilder
   constructor: (location) ->
-    @data = location?.uptimes
+    @data = $.extend({}, location?.uptimes)

   average: (@daysAgo = 30) ->
     return 0 unless (@data and @data.end)


Allow specifying to confirm_dialog where it should be attached.

--- app/assets/javascripts/transam_core/confirm_dialog.js.coffee
+++ app/assets/javascripts/transam_core/confirm_dialog.js.coffee
@@ -36,7 +36,10 @@
   $modal_html = $(modal_html)
   # Add the new button to the modal box
   $modal_html.find('.modal-footer').append($link)
-  element.after($modal_html)
+  attach = element.data('attach')
+  attach = $(attach)
+  # If they didn't specify where to attach, this just attaches at bottom of doc
+  attach.after($modal_html)
   # Pop it up
   $modal_html.modal()
   # Prevent the original link from working


Pricing: workflow was creating and not hiding its loader on initialization.

--- client/Main/CommonViews/formworkflow/formworkflow.coffee
+++ client/Main/CommonViews/formworkflow/formworkflow.coffee
@@ -136,6 +136,7 @@
     @addLoader()
     @prepareWorkflow?()
     @emit 'ready'
+    @hideLoader()

   skip: (data) ->
     v = new KDView


reactivity: update message fix

reactivity: update message fix

--- client/activity/lib/components/common/messagebody.coffee
+++ client/activity/lib/components/common/messagebody.coffee
@@ -27,10 +27,18 @@

     @content = content
     @renderEmojis()
-    @transformChannelHashtags()
+    @transformChannelHashtags @state.message

-  componentDidUpdate: -> @renderEmojis()
+  componentDidUpdate: (prevProps, prevState) ->
+
+    { message } = @props
+    # if message body is updated in props, we need to process hashtags
+    # and update state with a new message body
+    unless prevProps.message.get('body') is message.get('body')
+      return @transformChannelHashtags message
+
+    @renderEmojis()

   renderEmojis: ->
@@ -39,9 +47,7 @@
     emojify.run contentElement  if contentElement

-  transformChannelHashtags: ->
-
-    { message } = @state
+  transformChannelHashtags: (message) ->

     return  if message.has('isFake')
     return  unless message.has('body')


Fix bug with cleaning up application tags in the tests

--- test/application_tag.coffee
+++ test/application_tag.coffee
@@ -100,31 +100,31 @@
           done()

   it "should be able to show added files for all tags", (done) ->
-    done()
-    # tag1 = app.tags().build('1.0')
-    # tag1.save (err, reply) =>
-    #   assert.ifError err
-    #   files1 = tag1.files()
-    #   files1.save dup_files, (err, reply) =>
-    #     assert.ifError err
-    #     tag2 = app.tags().build('1.1')
-    #     tag2.save (err, reply) =>
-    #       assert.ifError err
-    #       files2 = tag2.files()
-    #       dup_files = [
-    #         { location: path.join(b_cp, "master.ipa"),   name: "master.ipa" },
-    #         { location: path.join(b_cp, "master.plist"), name: "master.plist" }
-    #       ]
+    tag1 = app.tags().build('1.0')
+    tag1.save (err, reply) =>
+      assert.ifError err
+      files1 = tag1.files()
+      files1.save dup_files, (err, reply) =>
+        assert.ifError err
+        tag2 = app.tags().build('1.1')
+        tag2.save (err, reply) =>
+          assert.ifError err
+          files2 = tag2.files()
+          dup_files = [
+            { location: path.join(b_cp, "master.ipa"),   name: "master.ipa" },
+            { location: path.join(b_cp, "master.plist"), name: "master.plist" }
+          ]

-    #       pfix = "#{__dirname}/fixtures"
-    #       fs.copy "#{pfix}/master.ipa", "#{b_cp}/1.0.ipa", (err) =>
-    #         fs.copy "#{pfix}/master.plist", "#{b_cp}/1.0.plist", (err) =>
-    #           files2.save dup_files, (err, reply) =>
-    #             tag2.all (err, reply) =>
-    #               assert.ifError err
-    #               ['1.0', '1.1'].should.include(reply['tags'][0]['name'])
-    #               ['1.0', '1.1'].should.include(reply['tags'][1]['name'])
-    #               done()
+          pfix = "#{__dirname}/fixtures"
+          fs.copy "#{pfix}/master.ipa", "#{b_cp}/master.ipa", (err) =>
+            fs.copy "#{pfix}/master.plist", "#{b_cp}/master.plist", (err) =>
+              files2.save dup_files, (err, reply) =>
+                assert.ifError err
+                tag2.all (err, reply) =>
+                  assert.ifError err
+                  ['1.0', '1.1'].should.include(reply['tags'][0]['name'])
+                  ['1.0', '1.1'].should.include(reply['tags'][1]['name'])
+                  done()

   it "should be able to remove a single tag from an app", (done) ->
     tags1 = app.tags().build('sillytag')


Strings

Strings

--- resources/assets/coffee/_classes/beatmap-discussion-helper.coffee
+++ resources/assets/coffee/_classes/beatmap-discussion-helper.coffee
@@ -97,7 +97,7 @@
       else
         ''

-    url
+    url.toString()

   # see @url


clear data after cancelling

clear data after cancelling

--- resources/assets/coffee/forum/topic-ajax.coffee
+++ resources/assets/coffee/forum/topic-ajax.coffee
@@ -35,7 +35,9 @@
   e.preventDefault()

   $postBox = $(e.target).parents '.js-forum-post'
-  $postBox.html $postBox.attr('data-original-post')
+  $postBox
+    .html $postBox.attr('data-original-post')
+    .attr 'data-original-post', null

   osu.pageChange()


Trigger change event on drop

Trigger change event on drop

--- resources/static/brunch/src/app/views/rules_tree_item_view.coffee
+++ resources/static/brunch/src/app/views/rules_tree_item_view.coffee
@@ -32,9 +32,9 @@
     @$(@el).droppable {
         hoverClass: 'dropHover'
       , drop: (event, ui) ->
-          # TODO: Update collection after drop
-          newVal = ui.draggable.find(".rule-text").html()
-          $(this).find("input[type='text']").val newVal
+          elem = $(this).find("input[type='text']")
+          elem.val ui.draggable.find(".rule-text").html()
+          elem.trigger('change')
       }
     @


Ace: file has changed logic added WIP

Ace: file has changed logic added WIP

--- client/landing/app/Applications/Ace.kdapplication/ace.coffee
+++ client/landing/app/Applications/Ace.kdapplication/ace.coffee
@@ -60,6 +60,9 @@
       unless err
         @notify "Successfully saved!", "success"
         @lastSavedContents = @lastContentsSentForSave
+        unless @askedForSave
+          log "this file has changed, put a modal and block editing @fatihacet!"
+        @askedForSave = no

     file.on "fs.save.started", =>
       @lastContentsSentForSave = @getContents()
@@ -109,6 +112,7 @@

     contents = @getContents()
     return @notify "Nothing to save!" unless contents isnt @lastSavedContents
+    @askedForSave = yes
     @emit "ace.requests.save", contents

   requestSaveAs:->


Keep event propagation going on

Keep event propagation going on

--- client/landing/site.landing/coffee/login/registerform.coffee
+++ client/landing/site.landing/coffee/login/registerform.coffee
@@ -22,7 +22,9 @@
         type             : "password"
         testPath         : "recover-password"
         placeholder      : "Password"
-        focus            : => @password.icon.unsetTooltip()
+        focus            : =>
+          @password.icon.unsetTooltip()
+          return yes
         keydown          : (event) =>
           if event.which is ENTER
             @password.input.validate()
@@ -49,9 +51,13 @@
           password        : @password
           tfcode          : @tfcode
         decorateValidation: no
-        focus             : => @email.icon.unsetTooltip()
+        focus             : =>
+          @email.icon.unsetTooltip()
+          return yes
+        blur              : =>
+          @fetchGravatarInfo @email.input.getValue()
+          return yes
         keydown           : (event) => @submitForm event  if event.which is ENTER
-        blur              : => @fetchGravatarInfo @email.input.getValue()
         change            : => @emailIsAvailable = no


handle /feature/:id/section=foo

handle /feature/:id/section=foo

--- apps/feature/index.coffee
+++ apps/feature/index.coffee
@@ -10,3 +10,4 @@
 app.set 'view engine', 'jade'

 app.get '/feature/:id', routes.index
+app.get '/feature/:id/*', routes.index


Improve behavior when directly loading #dataexplorer

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -28,7 +28,6 @@
             'click #reconnect': 'reconnect'
             'click .more_valid_results': 'show_more_results'
             'click .close': 'close_alert'
-            #TODO show warning

         displaying_full_view: false # Boolean for the full view (true if full view)

@@ -130,7 +129,10 @@
             for state of @suggestions
                 @suggestions[state].sort()

-            #TODO if codemirror has focus, trigger handle_keypress
+            if DataExplorerView.Container.prototype.focus_on_codemirror is true
+                # "@" refers to prototype -_-
+                # In case we give focus to codemirror then load the docs, we show the suggestion
+                window.router.current_view.handle_keypress()

         save_data_in_localstorage: =>
             if window.localStorage?
@@ -193,6 +195,7 @@
             @history = DataExplorerView.Container.prototype.history

             # Let's have a shortcut
+            @prototype = DataExplorerView.Container.prototype
             @saved_data = DataExplorerView.Container.prototype.saved_data
             @saved_data.show_query = @saved_data.query isnt @saved_data.current_query
             @current_results = @saved_data.results
@@ -306,16 +309,21 @@
                 lineNumbers: true
                 lineWrapping: true
                 matchBrackets: true
-            @codemirror.on 'blur', @hide_suggestion_and_description
+            @codemirror.on 'blur', @on_blur
             @codemirror.on 'gutterClick', @handle_gutter_click

             @codemirror.setSize '100%', 'auto'
             if @saved_data.current_query?
                 @codemirror.setValue @saved_data.current_query
             @codemirror.focus() # Give focus
+            @prototype.focus_on_codemirror = true
             @codemirror.setCursor @codemirror.lineCount(), 0
             @handle_keypress() # Show suggestions/description if there are
             @results_view.expand_raw_textarea()
+
+        on_blur: =>
+            @prototype.focus_on_codemirror = false
+            @hide_suggestion_and_description()

         # We have to keep track of a lot of things because web-kit browsers handle the events keydown, keyup, blur etc... in a strange way.
         current_suggestions: []
@@ -330,6 +338,7 @@
         # Core of the suggestions' system: We have to parse the query
         # Return true if we want code mirror to ignore the event
         handle_keypress: (editor, event) =>
+            @prototype.focus_on_codemirror = true
             # Let's hide the tooltip if the user just clicked on the textarea. We'll only display later the suggestions if there are (no description)
             if event?.type is 'mouseup'
                 @hide_suggestion_and_description()
@@ -415,7 +424,6 @@
             # TODO Make it flawless. I'm not sure that's the desired behavior
             if event?.type? and (event.type isnt 'keyup' and event.type isnt 'mouseup') or (event?.which? and event.which is 16) # We don't do anything for shift
                 return false
-
             @current_highlighted_suggestion = -1
             @.$('.suggestion_name_list').empty()