fix issue with game removal

fix issue with game removal

--- server.coffee
+++ server.coffee
@@ -27,17 +27,20 @@

 removePlayer = (socket, reason) ->
   for game, i in games
-    for player, j in game.players
-      if not player.user or player.id is socket.id
-        game.players.splice(j, 1)
-        if reason is "disconnect" and game.started
-          state = gameEngine.main.exec("disconnect", {gameid: game.gameid, text: "#{player.user.username} disconnected."})
-          lobby.to(game.gameid).emit("netrunner", {type: "state", state: state})
-        if reason is "leave"
-          socket.to(game.gameid).emit('netrunner', {type: "say", user: "__system__", text: "#{player.user.username} left the game."})
-        if game.players.length is 0
-          games.splice(i, 1)
-        break
+    if game
+      for player, j in game.players
+        if not player.user or player.id is socket.id
+          game.players.splice(j, 1)
+          if reason is "disconnect" and game.started
+            state = gameEngine.main.exec("disconnect", {gameid: game.gameid, text: "#{player.user.username} disconnected."})
+            lobby.to(game.gameid).emit("netrunner", {type: "state", state: state})
+          if reason is "leave"
+            socket.to(game.gameid).emit('netrunner', {type: "say", user: "__system__", text: "#{player.user.username} left the game."})
+          if game.players.length is 0
+            games.splice(i, 1)
+          break
+    else
+      games.splice(i, 1)
   lobby.emit('netrunner', {type: "games", games: games})

 # Socket.io


Use Log.info instead of Log.warn for messages.

--- server.coffee
+++ server.coffee
@@ -942,10 +942,10 @@
           timestamp: moment.utc().toDate()

       if migration instanceof @_RenameCollectionMigration
-        Log.warn "Renamed collection '#{ currentName }' to '#{ newName }'"
-        Log.warn "Migrated #{ counts.migrated }/#{ counts.all } document(s) (from #{ currentSchema } to #{ newSchema }): #{ migration.name }" if counts.all
+        Log.info "Renamed collection '#{ currentName }' to '#{ newName }'"
+        Log.info "Migrated #{ counts.migrated }/#{ counts.all } document(s) (from #{ currentSchema } to #{ newSchema }): #{ migration.name }" if counts.all
       else
-        Log.warn "Migrated #{ counts.migrated }/#{ counts.all } document(s) in '#{ currentName }' collection (from #{ currentSchema } to #{ newSchema }): #{ migration.name }" if counts.all
+        Log.info "Migrated #{ counts.migrated }/#{ counts.all } document(s) in '#{ currentName }' collection (from #{ currentSchema } to #{ newSchema }): #{ migration.name }" if counts.all

       currentSchema = newSchema
       currentName = newName
@@ -1037,9 +1037,9 @@
     updateAll = document._setupMigrations() or updateAll

   if updateAll
-    Log.warn "Migrations requested updating all references..."
+    Log.info "Migrations requested updating all references..."
     globals.Document.updateAll()
-    Log.warn "Done."
+    Log.info "Done"

 migrations = ->
   if globals.Document.Migrations.find({}, limit: 1).count() == 0
@@ -1065,23 +1065,23 @@
   # (Otherwise setupObservers would trigger strange exceptions anyway)
   globals.Document.defineAll()

-  Log.warn "Skipped migrations." if globals.Document.migrationsDisabled
+  Log.info "Skipped migrations" if globals.Document.migrationsDisabled
   # We still run the code to determine schema version and setup
   # observer to set schema version when inserting new documents,
   # but we then inside the code skip running migrations themselves
   migrations()

   if globals.Document.instanceDisabled
-    Log.warn "Skipped observers."
+    Log.info "Skipped observers"
     # To make sure everything is really skipped
     PREFIX = []
   else
     if globals.Document.instances is 1
-      Log.warn "Enabling observers..."
+      Log.info "Enabling observers..."
     else
-      Log.warn "Enabling observers, instance #{ INSTANCE }/#{ globals.Document.instances }, matching ID prefix: #{ PREFIX.join '' }"
+      Log.info "Enabling observers, instance #{ INSTANCE }/#{ globals.Document.instances }, matching ID prefix: #{ PREFIX.join '' }"
     setupObservers()
-    Log.warn "Done."
+    Log.info "Done"

 Document = globals.Document


silent mode

silent mode

--- server.coffee
+++ server.coffee
@@ -13,13 +13,14 @@

 #####

-CHAT_OFF = 'nochat' in process.argv
+config.nochat ?= 'nochat' in process.argv
+config.silent ?= 'silent' in process.argv

 sendChatMessage = (message) ->

     console.log "sending message: \n#{message}"

-    if CHAT_OFF then return
+    if config.nochat then return

     chatURL = "http://partychat-hooks.appspot.com/post/p_ngvimtvm?" + qs.stringify({ message }).replace(/\'/g, '%27')

@@ -50,7 +51,7 @@

 #####

-sendChatMessage 'Hello!'
+if not config.silent then sendChatMessage 'Hello!'

 server = http.createServer (req, res) ->


Fix ranking numbers

Fix ranking numbers

Should display correct ranking numbers now, but might not update
correctly when looking at the scores page when teams switch places.

--- server.coffee
+++ server.coffee
@@ -3,7 +3,7 @@
 Timer = require 'timer'
 Event = require 'event'

-# Global constants, use .peek()
+# Global constants, use global.<variable>
 global = {
 	pointsTime: 3600000
 }


Changes with mergebox enabled can change SessionDocumentView objects.

--- server.coffee
+++ server.coffee
@@ -12,11 +12,11 @@

     originalAdded = publish.added
     publish.added = (collectionName, id, fields) ->
-      return originalAdded.call @, collectionName, id, fields unless disabled
-
       stringId = @_idFilter.idStringify id

       FiberUtils.synchronize guardObject, "#{collectionName}#{stringId}", =>
+        return originalAdded.call @, collectionName, id, fields unless disabled
+
         collectionView = @_session.getCollectionView collectionName

         originalSessionDocumentView = collectionView.documents[stringId]
@@ -34,11 +34,11 @@

     originalChanged = publish.changed
     publish.changed = (collectionName, id, fields) ->
-      return originalChanged.call @, collectionName, id, fields unless disabled
-
       stringId = @_idFilter.idStringify id

       FiberUtils.synchronize guardObject, "#{collectionName}#{stringId}", =>
+        return originalChanged.call @, collectionName, id, fields unless disabled
+
         collectionView = @_session.getCollectionView collectionName

         originalSessionDocumentView = collectionView.documents[stringId]
@@ -61,11 +61,11 @@

     originalRemoved = publish.removed
     publish.removed = (collectionName, id) ->
-      return originalRemoved.call @, collectionName, id unless disabled
-
       stringId = @_idFilter.idStringify id

       FiberUtils.synchronize guardObject, "#{collectionName}#{stringId}", =>
+        return originalRemoved.call @, collectionName, id unless disabled
+
         collectionView = @_session.getCollectionView collectionName

         originalSessionDocumentView = collectionView.documents[stringId]


view options added

view options added

--- server.coffee
+++ server.coffee
@@ -40,6 +40,7 @@
   server.set 'port', process.env.PORT or config.port
   server.set 'views', path.join(__dirname, 'server', '/views')
   server.set 'view engine', 'jade'
+  server.set 'view options', { layout: false, pretty: false }
   server.use express.favicon()
   server.use express.logger('dev')
   server.use express.bodyParser()


:lipstick:

:lipstick:

--- src/packages/wrap-guide/lib/wrap-guide-view.coffee
+++ src/packages/wrap-guide/lib/wrap-guide-view.coffee
@@ -24,15 +24,13 @@
   getGuideColumn: (path) ->
     customColumns = config.get('wrapGuide.columns')
     return @getDefaultColumn() unless _.isArray(customColumns)
-    for customColumn in customColumns
-      continue unless _.isObject(customColumn)
-      pattern = customColumn['pattern']
-      continue unless pattern
-      return parseInt(customColumn['column']) if new RegExp(pattern).test(path)
+    for customColumn in customColumns when _.isObject(customColumn)
+      {pattern, column} = customColumn
+      return parseInt(column) if pattern and new RegExp(pattern).test(path)
     @getDefaultColumn()

   updateGuide: ->
-    column = @getGuideColumn(@editor.getPath(), @defaultColumn)
+    column = @getGuideColumn(@editor.getPath())
     if column > 0
       columnWidth = @editor.charWidth * column
       if columnWidth < @editor.layerMinWidth or columnWidth < @editor.width()


fix: :bug: fix ie11 issue inside appendCss function for direct player

change not supported function append to supported appendChild

--- src/javascripts/direct.coffee
+++ src/javascripts/direct.coffee
@@ -94,7 +94,7 @@
     style.rel = 'stylesheet'
     style.type = 'text/css'
     style.media = 'all'
-    document.querySelector('head').append(style)
+    document.querySelector('head').appendChild(style)

   setUpPlayers: () ->
     self = @


DataForm: Small fix for the 'add button' disable status. See #58269

--- src/elements/DataForm/DataForm.coffee
+++ src/elements/DataForm/DataForm.coffee
@@ -126,12 +126,15 @@

 	__updateView: ->
 		@__updateButtons()
-
-		if @_has_add_button
-			if @rows.some((row) -> row._empty)
-				@__addButton.disable()
-			else
-				@__addButton.enable()
+		@__updateAddButton()
+
+	__updateAddButton: ->
+		if not @_has_add_button
+			return
+		if @rows.some((row) -> row._empty)
+			@__addButton.disable()
+		else
+			@__addButton.enable()

 	__removeEmptyRows: ->
 		@__updateEmptyInRows()
@@ -190,6 +193,8 @@
 		for row in @rows
 			@__appendRow(row)

+		if @rows.length > 0
+			@__updateAddButton()
 		@__appendNewRow()
 		@__updateButtons()
 		return


Add tests

Add tests

--- test/tests.coffee
+++ test/tests.coffee
@@ -1 +1,55 @@
+assert = chai.assert
+# A simple spy function that expects a single argument. An optional `original`
+# function may be passed to the `spy` factory. Whenever the spy is called, it
+# will be called with the argument passed to the spy, and its return value will
+# be passed through.
+spy = (original) ->
+  fn = (opts) ->
+    fn.calls.push opts
+    original? opts
+  fn.calls = []
+  fn.callCount = -> fn.calls.length
+  # Asserts whether or not the spy was ever called with the provided argument.
+  # If it can't match the argument by value, it will attempt to compare it to
+  # spy calls as an 'options' object, checking to see if any of the provided
+  # options were in a set of options passed to a spy call.
+  fn.calledWith = (opts) ->
+    for callOpts in fn.calls
+      break if match = opts is callOpts
+      for opt of opts
+        break if not match = opt of callOpts and opts[opt] is callOpts[opt]
+    match
+  fn
+
+
 describe 'ReactComponentPlugin', ->
+  component = null
+
+  assertStartState = (expected) ->
+    assert.deepEqual component._tweenState, expected
+    assert component.setState.calledWith expected
+
+  assertEndState = (expected, done) ->
+    assert.deepEqual component._tweenState, expected
+    assert component.setState.calledWith expected
+    done()
+
+  beforeEach -> component = setState: spy()
+
+  it 'calls setState on a React component', (done) ->
+    TweenLite.set component, state: test: 1
+    assertEndState test: 1, done
+
+  it 'uses current state as initial value', (done) ->
+    component.state = test: 0
+    TweenLite.to component, 0.01,
+      state: {test: 1}
+      immediateRender: true
+      onStart: -> assertStartState test: 0
+      onComplete: -> assertEndState test: 1, done
+
+  it 'tweens from a state to a state', (done) ->
+    TweenLite.fromTo component, 0.01, {state: test: 0},
+      state: {test: 1}
+      onStart: -> assertStartState test: 0
+      onComplete: -> assertEndState test: 1, done


Stop dumping internal Storyboard messages to the console

--- src/listeners/wsClient.coffee
+++ src/listeners/wsClient.coffee
@@ -24,7 +24,7 @@
 _extensionRxMsg = (msg) ->
   {src, type, data} = msg
   return if src isnt 'DT'
-  console.log "[PG] RX #{src}/#{type}", data
+  ## console.log "[PG] RX #{src}/#{type}", data
   switch type
     when 'CONNECT_REQUEST', 'CONNECT_RESPONSE'
       _fExtensionReady = true


Improve robustness of log server

Improve robustness of log server

--- src/listeners/wsServer.coffee
+++ src/listeners/wsServer.coffee
@@ -29,13 +29,20 @@

   # Launch stand-alone log server
   if port?
-    expressApp = express()
-    expressApp.use express.static path.join(__dirname, '../../serverLogsApp')
-    httpServer = http.createServer expressApp
-    _ioStandalone = socketio(httpServer).of k.WS_NAMESPACE
-    _ioStandalone.on 'connection', (socket) -> _socketOnConnection socket, config
-    httpServer.listen port
-    mainStory.info LOG_SRC, "Server logs available on port #{chalk.cyan port}"
+    _httpInitError = (err) ->
+      mainStory.error LOG_SRC, "Error initialising standalong server logs on port #{chalk.cyan port}:", attach: err
+    try
+      expressApp = express()
+      expressApp.use express.static path.join(__dirname, '../../serverLogsApp')
+      httpServer = http.createServer expressApp
+      httpServer.on 'error', _httpInitError
+      httpServer.on 'listening', ->
+        mainStory.info LOG_SRC, "Server logs available on port #{chalk.cyan httpServer.address().port}"
+      _ioStandalone = socketio(httpServer).of k.WS_NAMESPACE
+      _ioStandalone.on 'connection', (socket) -> _socketOnConnection socket, config
+      httpServer.listen port
+    catch err
+      _httpInitError err

   # If a main application server is also provided,
   # launch another log server on the same application port
@@ -45,11 +52,16 @@
     _ioServerAdaptor = socketio(config.httpServer).of k.WS_NAMESPACE
   if _ioServerAdaptor
     _ioServerAdaptor.on 'connection', (socket) -> _socketOnConnection socket, config
+    _http2InitError = (err) ->
+      mainStory.error LOG_SRC, "Error initialising log server adaptor:", attach: err
     try
-      port2 = _ioServerAdaptor.server.httpServer.address().port
-      mainStory.info LOG_SRC, "Server logs available through main HTTP server on port #{chalk.cyan port2}"
-    catch
-      mainStory.info LOG_SRC, "Server logs available through main HTTP server (#{chalk.red 'port could not be determined'})"
+      httpServer2 = _ioServerAdaptor.server.httpServer
+      httpServer2.on 'error', _http2InitError
+      httpServer2.on 'listening', ->
+        port2 = httpServer2.address().port
+        mainStory.info LOG_SRC, "Server logs available through main HTTP server on port #{chalk.cyan port2}"
+    catch err
+      _http2InitError err
   return

 _socketOnConnection = (socket, config) ->


Add "->" as a search term separator

Add "->" as a search term separator

--- assets/javascripts/models/entry.coffee
+++ assets/javascripts/models/entry.coffee
@@ -1,7 +1,7 @@
 class app.models.Entry extends app.Model
   # Attributes: name, type, path

-  SEPARATORS_REGEXP = /\:?\ |#|::/g
+  SEPARATORS_REGEXP = /\:?\ |#|::|->/g
   PARANTHESES_REGEXP = /\(.*?\)$/

   constructor: ->


Time step and scroll

Time step and scroll

--- src/javascript/game/main_spike.coffee
+++ src/javascript/game/main_spike.coffee
@@ -169,8 +169,10 @@
         "p": 'toggle_pause'
         "d": 'toggle_bounding_box'
         "m": 'cycle_admin_mover'
-        ",": 'time_walk_back'
-        ".": 'time_walk_forward'
+        "<": 'time_walk_back'
+        ">": 'time_walk_forward'
+        ",": 'time_scroll_back'
+        ".": 'time_scroll_forward'
         "h": 'left'
         "j": 'down'
         "k": 'up'
@@ -284,7 +286,7 @@
         @ui.componentInspector.sync()
         @captureTimeWalkSnapShot()

-      if @time_walk_back
+      if @time_walk_back or @time_scroll_back
         @time_walk_back = false
         # [s0, s1, s2, s3, s4]
         @time_walk_index -= 1
@@ -301,7 +303,7 @@

         console.log "TIME WALK BACK!"

-      if @time_walk_forward
+      if @time_walk_forward or @time_scroll_forward
         @time_walk_forward = false
         # [s0, s1, s2, s3, s4]
         @time_walk_index += 1
@@ -366,6 +368,19 @@

       else if ac.time_walk_forward
         @time_walk_forward = true
+
+      else if ac.time_scroll_back
+        @time_scroll_forward = off
+        @time_scroll_back = true
+
+      else if ac.time_scroll_forward
+        @time_scroll_back = off
+        @time_scroll_forward = true
+
+      else
+        @time_scroll_back = off
+        @time_scroll_forward = off
+

     if ac.toggle_bounding_box


remove commented line

remove commented line

--- bokehjs/src/coffee/renderer/glyph/glyph_renderer.coffee
+++ bokehjs/src/coffee/renderer/glyph/glyph_renderer.coffee
@@ -92,12 +92,11 @@
       dtrender = Date.now() - trender
     else

+      # reset the selection mask
       tselect = Date.now()
-      # reset the selection mask
       selected_mask = {}
       for i in selected
         selected_mask[i] = true
-      #selected_mask = (i in selected for i in @all_indices)

       # intersect/different selection with render mask
       selected = new Array()


For project -- make client robust against server not having status info for a node (until I make that more robust)

--- salvus/page/project.coffee
+++ salvus/page/project.coffee
@@ -2048,13 +2048,14 @@
                                     l = loc
                                 replica.find(".salvus-project-replica-host").text(l)
                                 replica.find(".salvus-project-replica-datacenter").text(data.datacenter)
-                                if data.status.status != 'up'
-                                    replica.find(".salvus-project-replica-status").html('DOWN')
-                                    replica.css('background-color':'#ff0000', 'color':'#ffffff')
-                                else
-                                    replica.find(".salvus-project-replica-timeago").attr('title', data.newest_snapshot+".000Z").timeago()
-                                    stats = "#{data.status.ram_used_GB+data.status.ram_free_GB}GB RAM (#{data.status.ram_free_GB}GB free), #{data.status.load15} load, #{data.status.nprojects} running projects, #{data.status.nproc} cores"
-                                    replica.find(".salvus-project-replica-status").text(stats)
+                                if data.status?
+                                    if data.status.status != 'up'
+                                        replica.find(".salvus-project-replica-status").html('DOWN')
+                                        replica.css('background-color':'#ff0000', 'color':'#ffffff')
+                                    else
+                                        replica.find(".salvus-project-replica-timeago").attr('title', data.newest_snapshot+".000Z").timeago()
+                                        stats = "#{data.status.ram_used_GB+data.status.ram_free_GB}GB RAM (#{data.status.ram_free_GB}GB free), #{data.status.load15} load, #{data.status.nprojects} running projects, #{data.status.nproc} cores"
+                                        replica.find(".salvus-project-replica-status").text(stats)

                                 if loc == status.current_location
                                     replica.addClass("salvus-project-replica-current")


+ Drop and Restore (untested)

+ Drop and Restore (untested)

--- scripts/runAfterGit.coffee
+++ scripts/runAfterGit.coffee
@@ -11,7 +11,6 @@
 fs = require 'fs'
 zlib = require 'zlib'
 Deferred = require 'JQDeferred'
-#config = require '../server_config'

 #TODO: Could kill current coco server here.

@@ -54,14 +53,7 @@
   console.log "Downloading Database dump. It's big. This may take a while..."
   request = http.get dbDump, (response)->
     unzip = response.pipe(zlib.createGunzip()).pipe(tar.Extract(path: dbLocalPath))
-    unzip.on('error', (err) -> console.log "An error occurred while downloading DB Dump: " + err)
-    unzip.on 'end', ->
-      mongoose = require 'mongoose'
-      require('../server/commons/database').connect()
-      mongoose.connection.db.dropDatabase()
-      mongorestore = run 'mongorestore', ['dbLocalPath']
-      mongorestore.always ->
-        removeDir dbLocalPath
+    # Log download
     currentChunk = 0
     cur = 0
     len = parseInt(response.headers['content-length'], 10)
@@ -70,9 +62,19 @@
       cur += chunk.length
       console.log 'DB dump download received chunk ' + currentChunk++ + ", "  + (100.0 * cur / len).toFixed(2) + "% finished of " + total.toFixed(0) + " mb"

-unless '--skipupdate' in process.argv
+    unzip.on('error', (err) -> console.log "An error occurred while downloading DB Dump: " + err)
+    unzip.on 'end', ->
+      console.log "Finished downloading. Dropping Database"
+      mongodrop = run "mongo", ["coco", "--eval", "db.dropDatabase()"]
+      mongodrop.fail -> console.error "Error occurred"
+      mongodrop.always ->
+        console.log "Restoring from dump."
+        mongorestore = run "mongorestore", [dbLocalPath]
+        mongorestore.done ->
+          removeDir dbLocalPath
+
+unless '--skipupdates' in process.argv
   npm = if process.platform is "win32" then "npm.cmd" else "npm"
-
   npminstall = run npm, ['update']
   npminstall.done ->
     bowerinstall = run 'bower', ['update']


remove hardcoded path from require statement in KarmaEnvironment.coffee

--- lib/KarmaEnvironment.coffee
+++ lib/KarmaEnvironment.coffee
@@ -5,7 +5,7 @@
 temp      = require('temp').track()
 fs        = require 'fs'
 di        = require 'di'
-di_parse  = require('../node_modules/di/lib/annotation').parse
+di_parse  = require('di/lib/annotation').parse
 Q         = require 'q'
 _         = require 'lodash'
 heParser  = require './headerEnvironmentParser/parser'


Implement API resource for retrieving an individual vote.

--- routes/api/v1/votes.coffee
+++ routes/api/v1/votes.coffee
@@ -47,3 +47,33 @@
           return

         res.json(results)
+
+  # GET /api/v1/vote/:id - Retrieve all information on a particular vote.
+  app.get '/api/v1/vote/:id', (req, res) ->
+    try
+      voteObjectId = new ObjectId req.params.id
+    catch
+      errorOutput =
+        fieldId: "voteId",
+        error: "invalid",
+        message: "The vote id you requested was not a valid identifier. Identifiers should conform to MongoDB's ObjectID format."
+
+      res.json errorOutput, 417
+      return
+
+    db.collection("votes").findOne {_id: voteObjectId}, (err, vote) ->
+      if err
+        errorId = Math.random().toString(36).substring(7)
+        console.error("Error " + errorId + ": " + err)
+
+        res.json
+          id: errorId,
+          error: err
+        , 500
+
+        return
+
+      if vote?
+        res.json vote
+      else
+        res.send 404


add in some new mock responses for our staff grading API

--- lms/static/coffee/src/staff_grading/staff_grading.coffee
+++ lms/static/coffee/src/staff_grading/staff_grading.coffee
@@ -18,10 +18,15 @@

   mock: (cmd, data) ->
     # Return a mock response to cmd and data
+    # should take a problem id as an argument
     if cmd == 'get_next'
       @mock_cnt++
       response =
         success: true
+        problem_name: 'Problem 1'
+        num_left: 3
+        num_total: 5
+        prompt: 'This is a fake prompt'
         submission: 'submission! ' + @mock_cnt
         rubric: 'A rubric! ' + @mock_cnt
         submission_id: @mock_cnt
@@ -32,6 +37,16 @@
       console.log("eval: #{data.score} pts,  Feedback: #{data.feedback}")
       response =
         @mock('get_next', {})
+    # get_probblem_list
+    # sends in a course_id and a grader_id
+    # should get back a list of problem_ids, problem_names, num_left, num_total
+    else if cmd == 'get_problem_list'
+        response =
+            success: true
+            problem_list: [
+                {problem_id: 1, problem_name: "Problem 1", num_left: 3, num_total: 5}
+                {problem_id: 2, problem_name: "Problem 2", num_left: 1, num_total: 5}
+            ]
     else
       response =
         success: false


Removed console outputs

Removed console outputs

--- lib/atom-minify-view.coffee
+++ lib/atom-minify-view.coffee
@@ -224,8 +224,6 @@

         options = @obtainMinifierOptions(fileType, @options.jsMinifier)

-        console.log inputFilename
-
         params =
             type: if fileType == 'css' then @options.cssMinifier else @options.jsMinifier,
             fileIn: inputFilename,
@@ -291,7 +289,6 @@
         timestamp = new Date().getTime()
         filename = "atom-minify.#{timestamp}.#{filenameAddition}.tmp"
         filename = path.join(os.tmpdir(), filename)
-        console.log filename
         return filename


füge ausnahme für MS Office tmp dateien hinzu #3

--- lib/directory-upload.coffee
+++ lib/directory-upload.coffee
@@ -60,7 +60,7 @@
   #Lade die Datei ueber Daisy hoch
   upload: (filename,change) ->
     filePath = "#{@uploadDir}/#{filename}"
-    if filename? and fs.existsSync filePath and filename.indexOf '~$$'<0 #filename dosnt contain ~$$
+    if filename? and fs.existsSync filePath and filename.indexOf '~$$'<0 and filename.indexOf '.tmp'<0 and filename.indexOf 'Thumbs.db'<0 #filename dosnt contain ~$$
       # if isTextOrBinary.isTextSync filePath
       content = fs.readFileSync "#{@uploadDir}/#{filename}"


fix query for synctable by storage host

fix query for synctable by storage host

--- src/smc-hub/storage.coffee
+++ src/smc-hub/storage.coffee
@@ -1467,8 +1467,8 @@
                 table   : 'projects'
                 columns : FIELDS
                 where   :
-                    "host = $" : host
-                    "storage_request#>'{requested}' >= $" : age
+                    "storage#>>'{host}' = $" : host
+                    "storage_request#>>'{requested}' >= $" : age.toISOString()
                 cb    : (err, synctable) ->
                     if err
                         cb(err)
@@ -1601,7 +1601,7 @@
                     table   : 'projects'
                     columns : FIELDS
                     where   :
-                        "storage_request#>'{requested}' >= $" : age
+                        "storage_request#>>'{requested}' >= $" : age.toISOString()
                     cb      : (err, synctable) =>
                         if err
                             dbg("fail: #{err}")


Validate dynamic properties

Validate dynamic properties

--- src/test/test_world.coffee
+++ src/test/test_world.coffee
@@ -50,7 +50,10 @@
     t.end()

   test 'world has dynamic properties', (t) ->
-    world.put("fun", -> "wanna")
+    value = "wanna"
+    world.put("fun", -> value)
+    t.equal typeof world.get_raw("fun"), 'function', "raw function"
+    t.equal world.get("fun"), value, "dynamic property"
     t.end()


Better config time parse.

Better config time parse.

--- lib/event-watch-view.coffee
+++ lib/event-watch-view.coffee
@@ -60,18 +60,23 @@
     if !dt
       dt = new Date()

-    time = timeStr.match(/(\d+)(?::(\d\d))?\s*(p?)/i)
+    time = timeStr.match(/(\d+)(?::(\d\d))?\s*(am|pm)?/i)
     if !time
       return NaN

-    hours = parseInt(time[1], 10)
-    if hours == 12 && !time[3]
-        hours = 0
+    hour = parseInt(time[1], 10)
+    minute = parseInt(time[2], 10) || 0
+    ampm = time[3]
+    am = (!ampm || ampm.toLowerCase() == 'am')
+    pm = (ampm && ampm.toLowerCase() == 'pm')
+
+    if hour == 12 && am
+        hour = 0
     else
-      hours += (hours < 12 && time[3]) ? 12 : 0
+      hour += (hour < 12 && pm) ? 12 : 0

-    dt.setHours(hours)
-    dt.setMinutes(parseInt(time[2], 10) || 0)
+    dt.setHours(hour)
+    dt.setMinutes(minute)
     dt.setSeconds(0, 0)
     return dt


Add create view helpers

Add create view helpers

--- lib/grammar-selector.coffee
+++ lib/grammar-selector.coffee
@@ -1,18 +1,20 @@
-GrammarStatusView = require './grammar-status-view'
-GrammarSelectorView = require './grammar-selector-view'
-
 module.exports =
   activate: ->
-    atom.workspaceView.command 'grammar-selector:show', =>
-      editor = atom.workspace.getActiveEditor()
-      new GrammarSelectorView(editor) if editor?
+    atom.workspaceView.command 'grammar-selector:show', ->
+      createGrammarSelectorView()

-    createStatusEntry = ->
-      view = new GrammarStatusView(atom.workspaceView.statusBar)
-      atom.workspaceView.statusBar.appendLeft(view)
+    atom.packages.once 'activated', ->
+      createGrammarStatusView()

-    if atom.workspaceView.statusBar?
-      createStatusEntry()
-    else
-      atom.packages.once 'activated', ->
-        createStatusEntry()
+createGrammarSelectorView = ->
+  editor = atom.workspace.getActiveEditor()
+  if editor?
+    GrammarSelectorView = require './grammar-selector-view'
+    new GrammarSelectorView(editor)
+
+createGrammarStatusView = ->
+  statusBarView = atom.workspaceView.statusBar
+  if statusBarView?
+    GrammarStatusView = require './grammar-status-view'
+    view = new GrammarStatusView(statusBarView)
+    statusBarView.appendLeft(view)


Support text.md for special indentation handling of symbols provider

--- lib/provider/symbols.coffee
+++ lib/provider/symbols.coffee
@@ -28,7 +28,7 @@

   getLevelForRow: (row) ->
     # For gfm source, determine level by counting leading '#' chars( use header level ).
-    if @editor.getGrammar().scopeName is 'source.gfm'
+    if @editor.getGrammar().scopeName in ['source.gfm', 'text.md']
       lineText = @editor.lineTextForBufferRow(row)
       (lineText.match(/#+/)?[0]?.length - 1) ? 0
     else


Guard again task already terminated

Guard again task already terminated

Closes #40

--- lib/spell-check-task.coffee
+++ lib/spell-check-task.coffee
@@ -16,4 +16,4 @@
       @constructor.task = null

   start: (args...) ->
-    @constructor.task.start(args...)
+    @constructor.task?.start(args...)


Make Bob Ross less annoying, fix infinite loop

--- scripts/nostalgia.coffee
+++ scripts/nostalgia.coffee
@@ -91,8 +91,9 @@

         firstQuote = (res.random memories[name])
         secondQuote = (res.random memories[name])
-        while secondQuote == firstQuote
-            secondQuote = (res.random memories[name])
+        if memories[name].length > 1
+            while secondQuote == firstQuote
+                secondQuote = (res.random memories[name])

         personQuotes = []
         personQuotes.push "#{name}: " + firstQuote
@@ -283,7 +284,7 @@
     robot.respond /end guess who/i, endGuessWhoRespond
     robot.respond /give up/i, giveUpRespond

-    robot.hear /.*a bug.*/i, bobRossRespond
+    robot.hear /.*that's a bug.*/i, bobRossRespond

 `// All code below from http://www.atrixnet.com/bs-generator.html, I take no credit for it
 function randomarray(a) {


existential

existential

--- node_modules_koding/kite-amqp/lib/kite-amqp/index.coffee
+++ node_modules_koding/kite-amqp/lib/kite-amqp/index.coffee
@@ -39,7 +39,7 @@
     @unscrubMessage routingKey, authenticatedUser, scrubbed, (unscrubbed)=>
       [data, callback] = unscrubbed
       data.withArgs?.username = authenticatedUser
-      {method, withArgs} = data
+      {method, withArgs} = data  if data?
       withArgs or= {}
       switch method
         when 'ping' then @handleResponse routingKey, callback.id, 1


Add Collection first and last methods

Add Collection first and last methods

--- src/marbles/collection.js.coffee
+++ src/marbles/collection.js.coffee
@@ -51,6 +51,14 @@
     @trigger('prepend:complete', models)
     models

+  first: =>
+    return unless cid = @model_ids[0]
+    @constructor.model.find({ cid: cid })
+
+  last: =>
+    return unless cid = _.last(@model_ids)
+    @constructor.model.find({ cid: cid })
+
   unshift: (models...) =>
     for model in models
       @model_ids.unshift(model.cid)


:bug: selectBlockwise select incorrect range in some situation.

Degraded when fixing another bug. I need to write spec honestly!

--- lib/visual-blockwise.coffee
+++ lib/visual-blockwise.coffee
@@ -150,12 +150,12 @@
     tail = s.getTailBufferPosition()
     head = s.getHeadBufferPosition()
     {start, end} = s.getBufferRange()
+    [action, step] = if s.isReversed() then ['Up', -1] else ['Down', +1]

     range = new Range(tail, [tail.row, head.column])
     range = range.translate([0, -1], [0, +1]) if start.column >= end.column
+
     s.setBufferRange(range, reversed: head.column < tail.column)
-    [action, step] = if s.isReversed() then ['Up', -1] else ['Down', +1]
-
     # NOTE: Need to skip the amount of rows where no selectable chars exist.
     _.times (end.row - start.row), =>
       range = range.translate([step, 0], [step, 0])


fix: typo

fix: typo

--- app/components/vnc-viewer.coffee
+++ app/components/vnc-viewer.coffee
@@ -99,7 +99,7 @@
       if rfb._rfb_connection_state is 'connected'
         rfb.disconnect()
         oldCanvas = @element.getElementsByClassName("canvas")[0]
-        ele.remove()
+        oldCanvas.remove()
         canvasContainer = @element.getElementsByClassName(".canvas-container")[0]
         newCanvas = document.createElement("canvas")
         newCanvas.setAttribute("class", "canvas" )


[market] is finite test to prevent balance click from showing NaN when there is no market data

--- app/js/controllers/market.coffee
+++ app/js/controllers/market.coffee
@@ -223,7 +223,7 @@
         makeweight = get_makeweight()
         coalesce = (new_value, old_value, precision) ->
             TradeData = MarketService.TradeData
-            ret = if new_value then new_value else TradeData.helper.to_float(old_value)
+            ret = if new_value and isFinite(new_value) then new_value else TradeData.helper.to_float(old_value)
             if ret == 0 # TODO, instead of nulls for validation, use (<input min="... )
                 return null
             else


Don't store ThangState when simulating headlessly (better performance)

--- app/lib/world/world_frame.coffee
+++ app/lib/world/world_frame.coffee
@@ -19,12 +19,12 @@
     return nextFrame

   setState: ->
-    return if @world.synchronous
+    return if @world.synchronous or @world.headless
     for thang in @world.thangs when not thang.stateless
       @thangStateMap[thang.id] = thang.getState()

   restoreState: ->
-    return if @world.synchronous
+    return if @world.synchronous or @world.headless
     thangState.restore() for thangID, thangState of @thangStateMap
     for thang in @world.thangs
       if not @thangStateMap[thang.id] and not thang.stateless
@@ -32,11 +32,11 @@
         thang.exists = false

   restorePartialState: (ratio) ->
-    return if @world.synchronous
+    return if @world.synchronous or @world.headless
     thangState.restorePartial ratio for thangID, thangState of @thangStateMap

   restoreStateForThang: (thang) ->
-    return if @world.synchronous
+    return if @world.synchronous or @world.headless
     thangState = @thangStateMap[thang.id]
     if not thangState
       if not thang.stateless


apply missed fixes

apply missed fixes

--- client/activity/lib/views/embedboxwidget.coffee
+++ client/activity/lib/views/embedboxwidget.coffee
@@ -49,7 +49,7 @@

     @embedLinks = new EmbedBoxLinksView { delegate: this }

-    @embedLinks.on 'LinkAdded', ({ url }) =>
+    @embedLinks.on 'LinkAdded', (url) =>
       @show()
       # if the embed index isn't set, set it to 0
       @setEmbedIndex 0  unless @getEmbedIndex()?
@@ -76,9 +76,11 @@

     fn = @bound 'checkInputForUrls'

+    delay = 1000
+    timer = null
     input.on 'keydown', (event) ->
-      fn()  if event.which in [9, 13, 32]
-
+      kd.utils.killWait timer
+      timer = kd.utils.wait delay, fn
     input.on 'paste', fn
     input.on 'change', fn


docs: Corrected type of string list

docs: Corrected type of string list

--- client/app/lib/providers/snapshothelpers.coffee
+++ client/app/lib/providers/snapshothelpers.coffee
@@ -28,7 +28,7 @@
  * label is not unique, it is returned in the format of "label index".
  *
  * @params {String} label - The source label.
- * @params {String[]} labels - The labels to avoid.
+ * @params {Array.<String>} labels - The labels to avoid.
  * @returns {String} label - A formatted label, not in the labels list.
 ###
 getUniqueLabel = (label, labels = []) ->


setting innerhtml for tokens without remove button

--- vendor/assets/javascripts/token-text-area.coffee
+++ vendor/assets/javascripts/token-text-area.coffee
@@ -246,6 +246,8 @@
       node.setAttribute('data-id', id)
       if @options.removeButton
         node.innerHTML = '' + name + ''
+      else
+        node.innerHTML = name

     @range.insertNode(node)


Yoast snippet properly reflects the title Artsy uses for Google

--- client/apps/edit/components/layout/index.coffee
+++ client/apps/edit/components/layout/index.coffee
@@ -190,7 +190,7 @@

     yoastView = new YoastView
       contentField: @fullText
-      title: @article.get('title')
+      title: @article.get('thumbnail_title')
       slug: @article.getSlug()

   onKeyup: =>


Fixing promise-based matchers, since they can't be async

--- test/spec/roadmapplanningboard/UuidMapperSpec.coffee
+++ test/spec/roadmapplanningboard/UuidMapperSpec.coffee
@@ -42,8 +42,7 @@

     it 'should reject with an error if a uuid cannot be found', ->
       promise = @uuidMapper.getUuid(ObjectID: 3)
-      promise.always (value) =>
-        expect(promise).toRejectWith 'uuid not found'
+      @waitForPromiseToRejectWith(promise, 'uuid not found')

     it 'should not make a request for the same uuid more than once', ->
       @uuidMapper.getUuid(@mockWorkspace).then (uuid) =>


Fix [#50787131], not sure how this ever worked without _.bind

--- app/assets/javascripts/views/weekly_aggregates.js.coffee
+++ app/assets/javascripts/views/weekly_aggregates.js.coffee
@@ -19,8 +19,8 @@
       if @isInViewport()
         @render()
       else
-        _.delay @render, 200
-
+        _.delay _.bind(@render, @), 200
+
     @on "week:updated", @render

   # TODO: Stuff this in workers or use slices or do something less dumb than doing it serially


CNS-540: The system does not validate a blank input Discussions

--- app/coffeescripts/views/DiscussionTopics/EditView.coffee
+++ app/coffeescripts/views/DiscussionTopics/EditView.coffee
@@ -156,7 +156,7 @@

     getFormData: ->
       data = super
-      data.title ||= I18n.t 'default_discussion_title', 'No Title'
+      #data.title ||= I18n.t 'default_discussion_title', 'No Title'
       data.discussion_type = if data.threaded is '1' then 'threaded' else 'side_comment'
       data.podcast_has_student_posts = false unless data.podcast_enabled is '1'

@@ -236,6 +236,10 @@
     )

     validateBeforeSave: (data, errors) =>
+      if data.title == ''
+        errors["title"] = [
+          message: I18n.t 'title_required', 'Title is required'
+        ]
       if data.delay_posting == "0"
         data.delayed_post_at = null
       if @isTopic() && data.set_assignment is '1'


a number of refactors to the Lexer. It should be a good bit clearer to read now.

--- lexer.coffee
+++ lexer.coffee
@@ -89,12 +89,12 @@

   # Scan by attempting to match tokens one character at a time. Slow and steady.
   tokenize: (code) ->
-    @code    : code       # Cleanup code by remove extra line breaks, TODO: chomp
-    @i       : 0          # Current character position we're parsing
-    @line    : 1          # The current line.
-    @indent  : 0          # The current indent level.
-    @indents : []         # The stack of all indent levels we are currently within.
-    @tokens  : []         # Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
+    @code    : code  # The remainder of the source code.
+    @i       : 0     # Current character position we're parsing.
+    @line    : 1     # The current line.
+    @indent  : 0     # The current indent level.
+    @indents : []    # The stack of all indent levels we are currently within.
+    @tokens  : []    # Collection of all parsed tokens in the form ['TOKEN_TYPE', value]
     while @i < @code.length
       @chunk: @code.slice(@i)
       @extract_next_token()
@@ -120,18 +120,12 @@
   # Matches identifying literals: variables, keywords, method names, etc.
   identifier_token: ->
     return false unless id: @match IDENTIFIER, 1
-    @tag(1, 'PROTOTYPE_ACCESS') if @value() is '::'
-    if @value() is '.' and not (@value(2) is '.')
-      if @tag(2) is '?'
-        @tag(1, 'SOAK_ACCESS')
-        @tokens.splice(-2, 1)
-      else
-        @tag(1, 'PROPERTY_ACCESS')
+    @name_access_type()
     tag: 'IDENTIFIER'
-    tag:  id.toUpperCase() if KEYWORDS.indexOf(id) >= 0 and
-      not ((ACCESSORS.indexOf(@tag()) >= 0) and not @prev().spaced)
-    throw new Error('SyntaxError: Reserved word "' + id + '" on line ' + @line) if RESERVED.indexOf(id) >= 0
-    tag: 'LEADING_WHEN' if tag is 'WHEN' and BEFORE_WHEN.indexOf(@tag()) >= 0
+    tag: id.toUpperCase() if include(KEYWORDS, id) and
+      not (include(ACCESSORS, @tag(0)) and not @prev().spaced)
+    @identifier_error id  if include RESERVED, id
+    tag: 'LEADING_WHEN'   if tag is 'WHEN' and include BEFORE_WHEN, @tag()
     @token(tag, id)
     @i += id.length
     true
@@ -155,11 +149,7 @@
   # Matches heredocs, adjusting indentation to the correct level.
   heredoc_token: ->
     return false unless match = @chunk.match(HEREDOC)
-    doc: match[2] or match[4]
-    indent: (doc.match(HEREDOC_INDENT) or ['']).sort()[0]
-    doc: doc.replace(new RegExp("^" + indent, 'gm'), '')
-            .replace(MULTILINER, "\\n")
-            .replace(/"/g, '\\"')
+    doc: @sanitize_heredoc match[2] or match[4]
     @token 'STRING', '"' + doc + '"'
     @line += @count match[1], "\n"
     @i += match[1].length
@@ -175,7 +165,7 @@
   # Matches regular expression literals.
   regex_token: ->
     return false unless regex: @match REGEX, 1
-    return false if NOT_REGEX.indexOf(@tag()) >= 0
+    return false if include NOT_REGEX, @tag()
     @token 'REGEX', regex
     @i += regex.length
     true
@@ -194,10 +184,11 @@
     return false unless indent: @match MULTI_DENT, 1
     @line += indent.match(MULTILINER).length
     @i    += indent.length
-    next_character: @chunk.match(MULTI_DENT)[4]
     prev: @prev(2)
     size: indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length
-    no_newlines: next_character is '.' or (@value() and @value().match(NO_NEWLINE) and prev and (prev[0] isnt '.') and not @value().match(CODE))
+    next_character: @chunk.match(MULTI_DENT)[4]
+    no_newlines: next_character is '.' or (@value() and @value().match(NO_NEWLINE) and
+      prev and (prev[0] isnt '.') and not @value().match(CODE))
     if size is @indent
       return @suppress_newlines(indent) if no_newlines
       return @newline_token(indent)
@@ -211,7 +202,7 @@
     @indent: size
     true

-  # Record an oudent token or tokens, if we're moving back inwards past
+  # Record an outdent token or tokens, if we're moving back inwards past
   # multiple recorded indents.
   outdent_token: (move_out, no_newlines) ->
     while move_out > 0 and @indents.length
@@ -221,7 +212,8 @@
     @token 'TERMINATOR', "\n" unless @tag() is 'TERMINATOR' or no_newlines
     true

-  # Matches and consumes non-meaningful whitespace.
+  # Matches and consumes non-meaningful whitespace. Tag the previous token
+  # as being "spaced", because there are some cases where it matters.
   whitespace_token: ->
     return false unless space: @match WHITESPACE, 1
     prev: @prev()
@@ -252,7 +244,7 @@
     tag: value
     if value.match(ASSIGNMENT)
       tag: 'ASSIGN'
-      throw new Error('SyntaxError: Reserved word "' + @value() + '" on line ' + @line + ' can\'t be assigned') if JS_FORBIDDEN.indexOf(@value()) >= 0
+      @assignment_error() if include JS_FORBIDDEN, @value
     else if value is ';'
       tag: 'TERMINATOR'
     else if value is '[' and @tag() is '?' and not_spaced
@@ -262,12 +254,41 @@
     else if value is ']' and @soaked_index
       tag: 'SOAKED_INDEX_END'
       @soaked_index: false
-    else if CALLABLE.indexOf(@tag()) >= 0 and not_spaced
+    else if include(CALLABLE, @tag()) and not_spaced
       tag: 'CALL_START'  if value is '('
       tag: 'INDEX_START' if value is '['
     @token tag, value
     @i += value.length
     true
+
+  # Token Manipulators ==================================================
+
+  # As we consume a new IDENTIFIER, look at the previous token to determine
+  # if it's a special kind of access.
+  name_access_type: ->
+    @tag(1, 'PROTOTYPE_ACCESS') if @value() is '::'
+    if @value() is '.' and not (@value(2) is '.')
+      if @tag(2) is '?'
+        @tag(1, 'SOAK_ACCESS')
+        @tokens.splice(-2, 1)
+      else
+        @tag 1, 'PROPERTY_ACCESS'
+
+  # Sanitize a heredoc by escaping double quotes and erasing all external
+  # indentation on the left-hand side.
+  sanitize_heredoc: (doc) ->
+    indent: (doc.match(HEREDOC_INDENT) or ['']).sort()[0]
+    doc.replace(new RegExp("^" + indent, 'gm'), '')
+       .replace(MULTILINER, "\\n")
+       .replace(/"/g, '\\"')
+
+  # When you try to use a forbidden word in JavaScript as an identifier.
+  identifier_error: (word) ->
+    throw new Error 'SyntaxError: Reserved word "' + word + '" on line ' + @line
+
+  # When you try to assign to a reserved word in JavaScript, like "function".
+  assignment_error: ->
+    throw new Error 'SyntaxError: Reserved word "' + @value() + '" on line ' + @line + ' can\'t be assigned'

   # Helpers =============================================================

@@ -327,3 +348,8 @@
   # axe it.
   close_indentation: ->
     @outdent_token(@indent)
+
+# Helper functions:
+
+# Does a list include a value?
+include: (list, value) -> list.indexOf(value) >= 0


Added string interpolation for identifiers

--- lexer.coffee
+++ lexer.coffee
@@ -166,7 +166,7 @@
   string_token: ->
     return false unless string: @match STRING, 1
     escaped: string.replace STRING_NEWLINES, " \\\n"
-    @token 'STRING', escaped
+    @interpolate_string escaped
     @line += count string, "\n"
     @i += string.length
     true
@@ -340,6 +340,36 @@
   assignment_error: ->
     throw new Error 'SyntaxError: Reserved word "' + @value() + '" on line ' + @line + ' can\'t be assigned'

+  # Replace variables and block calls inside double-quoted strings.
+  interpolate_string: (escaped) ->
+    if escaped.length < 3 or escaped.indexOf('"') isnt 0
+      @token 'STRING', escaped
+    else
+      tokens: []
+      quote: escaped.substring(0, 1)
+      escaped: escaped.substring(1, escaped.length - 1)
+      while escaped.length
+        identifier_match: escaped.match /(^|[\s\S]*?(?:[\\]|\\\\)?)(\$([a-zA-Z_]\w*))/
+        if identifier_match
+          [group, before, identifier]: identifier_match
+          if before.substring(before.length - 1) is '\\'
+            tokens.push ['STRING', quote + before.substring(0, before.length - 1) + identifier + quote] if before.length
+          else
+            tokens.push ['STRING', quote + before + quote] if before.length
+            tokens.push ['IDENTIFIER', identifier.substring(1)]
+          escaped: escaped.substring(group.length)
+        else
+          tokens.push ['STRING', quote + escaped + quote]
+          escaped: ''
+      if tokens.length > 1
+        for i in [tokens.length - 1..1]
+          if tokens[i][0] is 'STRING' and tokens[i - 1][0] is 'STRING'
+            tokens.splice i - 1, 2, ['STRING', quote + tokens[i - 1][1].substring(1, tokens[i - 1][1].length - 1) +
+                                                       tokens[i][1].substring(1, tokens[i][1].length - 1) + quote]
+      for each, i in tokens
+        @token each[0], each[1]
+        @token '+', '+' if i < tokens.length - 1
+
   # Helpers
   # -------


Poops it is

Poops it is

--- scripts/poo-tracker.coffee
+++ scripts/poo-tracker.coffee
@@ -1,8 +1,8 @@
 Redis = require 'redis'
 Url = require 'url'

-POO_TRACKER_KEY = "poo:skalnik:message"
-POO_LATEST_KEY = "poo:skalnik:latest_message"
+POO_TRACKER_KEY = "poops"
+POO_LATEST_KEY = "poops:latest_message"

 info = Url.parse process.env.POO_REDIS_URL
 redis_client = Redis.createClient(info.port, info.hostname)


Don't throw error if progress is set to the same time.

--- packages/landsofillusions/memory/progress/methods.coffee
+++ packages/landsofillusions/memory/progress/methods.coffee
@@ -18,7 +18,10 @@
   existingObservedMemory = _.find progress.observedMemories, (observedMemory) -> observedMemory.memory._id is memoryId

   if existingObservedMemory
-    throw new AE.ArgumentException "Time can only be increased." unless time > existingObservedMemory.time
+    throw new AE.ArgumentException "Time can't be decreased." if time < existingObservedMemory.time
+
+    # Nothing to do if we're not increasing it.
+    return if time.getTime() is existingObservedMemory.time.getTime()

     LOI.Memory.Progress.documents.update
       _id: progress._id


Social: add glancePinnedPost and  updateLastSeenTime functions

--- client/Main/socialapicontroller.coffee
+++ client/Main/socialapicontroller.coffee
@@ -286,3 +286,9 @@
       channelApiChannelsResFunc 'searchTopics', args...
     fetchProfileFeed     : (args...)->
       channelApiActivitiesResFunc 'fetchProfileFeed', args...
+    glancePinnedPost     : (options, callback)->
+      return callback { message: "MessageId is not set" } unless options.messageId
+      KD.remote.api.SocialChannel.glancePinnedPost options, callback
+    updateLastSeenTime   : (options, callback)->
+      return callback { message: "Channel Id is not set" } unless options.channelId
+      KD.remote.api.SocialChannel.updateLastSeenTime options, callback


no need these

no need these

--- client/app/CommonViews/LikeView.coffee
+++ client/app/CommonViews/LikeView.coffee
@@ -89,10 +89,10 @@

 class LikeViewClean extends LikeView

-  constructor:(options={ tooltipPosition : 'w' }, data)->
+  constructor:->

     @seperator = new KDCustomHTMLView "span"
-    super options, data
+    super

     @seperator.updatePartial if @getData().meta.likes then ' · ' else ''


Kloud: managed vms can only get info from their klient kites, not from kloud

--- client/app/lib/kite/kites/kloud.coffee
+++ client/app/lib/kite/kites/kloud.coffee
@@ -63,7 +63,7 @@
       @needsRequest[machineId] = no

       # This is for tests, it bypasses klient info state
-      if @_disableKlientInfo
+      if @_disableKlientInfo and not isManaged machineId
         @askInfoFromKloud machineId, currentState
       else
         @askInfoFromKlient machineId, (klientInfo)=>
@@ -94,6 +94,7 @@
     if not machine or not machineId
       return callback null

+    managed    = machine.provider is 'managed'
     klientKite = klient?[machine.uid]

     unless klientKite?
@@ -106,25 +107,29 @@
           correlationName : machine.uid

       else
-        return callback null
+        return callback if managed
+        then State: Machine.State.Stopped, via: 'klient'
+        else null

     klientKite.ping()

       .then (res)->

         if res is 'pong'
-        then callback State: Machine.State.Running, via: 'klient'
+          callback State: Machine.State.Running, via: 'klient'
         else
           computeController.invalidateCache machineId
           callback null

       .timeout 5000

-      .catch ->
+      .catch (err)->

-        KiteLogger.failed 'klient', 'kite.ping'
-
-        callback null
+        if err?.name is 'TimeoutError' and managed
+          callback State: Machine.State.Stopped, via: 'klient'
+        else
+          KiteLogger.failed 'klient', 'kite.ping'
+          callback null

   askInfoFromKloud: (machineId, currentState) ->


Providers.config: add advancedFields for AWS

--- client/app/lib/providers/config.coffee
+++ client/app/lib/providers/config.coffee
@@ -19,6 +19,11 @@
     link                   : "https://aws.amazon.com"
     title                  : "AWS Credential"
     description            : "Amazon Web Services"
+    advancedFields         : [
+                              'subnet', 'sg', 'vpc',
+                              'ami','acl','cidr_block',
+                              'igw','key_pair','rtb'
+                             ]
     credentialFields       :
       access_key           :
         label              : "Access Key"


IDE: Fix resizing issue of the IDE when Koding sidebar collapsed.

This is not an ideal fix because when Koding sidebar collapsed IDE sidebar changes its width automatically, it shouldn't. I will make it better when I have time. kib, bye.

--- client/ide/views/tabview/idefilestabview.coffee
+++ client/ide/views/tabview/idefilestabview.coffee
@@ -98,5 +98,7 @@
       dblclick   : (event) ->
         KD.utils.stopDOMEvent event
         mainView.toggleSidebar()
+        KD.utils.wait 233, ->
+          KD.singletons.windowController.notifyWindowResizeListeners()

     @tabView.addSubView handle


removed unused methods

removed unused methods

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -207,10 +207,6 @@
           (signature String, Function)
         transferOwnership:
           (signature String, Function)
-        fetchRolesByClientId: [
-          (signature Function)
-          (signature String, Function)
-        ]
         # fetchInvitationsFromGraph:
         #   (signature String, Object, Function)
         # countInvitationsFromGraph:
@@ -258,8 +254,6 @@
         fetchUserStatus:
           (signature Object, Function)
         fetchInvitationsByStatus:
-          (signature Object, Function)
-        toggleFeature:
           (signature Object, Function)
     schema          :
       title         :
@@ -832,19 +826,6 @@
       kallback()

-  fetchRolesByClientId:(clientId, callback)->
-    [callback, clientId] = [clientId, callback]  unless callback
-    return callback null, []  unless clientId
-
-    JSession = require '../session'
-    JSession.one {clientId}, (err, session)=>
-      return callback err  if err
-      {username} = session.data
-      return callback null, []  unless username
-
-      @fetchMembershipStatusesByUsername username, (err, roles)=>
-        callback err, roles or [], session
-
   createRole: permit 'grant permissions',
     success:(client, formData, callback)->
       JGroupRole = require './role'
@@ -908,7 +889,6 @@
       convertPrivateToPublic this, client
     @privacy = privacy

-  getPrivacy:-> @privacy

   modify: permit
     advanced : [
@@ -953,42 +933,6 @@
           else if explanation? then ERROR_POLICY
           else ERROR_NO_POLICY
         callback clientError, no
-
-  toggleFeature: permit 'grant permissions',
-    success:(client, options, callback)->
-      if not options.feature or not options.role or not options.operation
-        return callback {message:"request is not valid"}
-
-      @disabledFeatures = {}  unless @disabledFeatures
-      @disabledFeatures[options.role] =[]  unless @disabledFeatures[options.role]
-
-      if options.operation is "disable"
-        if options.feature not in @disabledFeatures?[options.role]
-          @disabledFeatures[options.role].push options.feature
-          return @update callback
-        else
-          return callback {message:"item is not in the list "}
-      else
-
-        if options.feature not in @disabledFeatures?[options.role]
-          return callback {message:"item is not in the list"}
-        else
-          ops = (feature for feature in @disabledFeatures?[options.role] when feature isnt options.feature)
-          @disabledFeatures[options.role] = ops
-          return @update callback
-
-  fetchAccountByEmail: (email, callback) ->
-    JUser    = require '../user'
-    JUser.one {email}, (err, user)=>
-      return callback err, null  if err or not user
-      user.fetchOwnAccount (err, account)=>
-        return callback err, null  if err
-        return callback new Error "Account not found.", null  unless account
-        @isMember account, (err, isMember)->
-          if isMember
-            callback new KodingError "#{email} is already member of this group!"
-          else
-            callback null, account

   # saveInviteMessage: permit 'send invitations',
@@ -1069,14 +1013,6 @@
           else unless request? then callback null, ['guest']
           else callback null, ["invitation-#{request.status}"]

-  fetchMembershipStatusesByUsername: (username, callback)->
-    JAccount = require '../account'
-    JAccount.one {'profile.nickname': username}, (err, account)=>
-      if not err and account
-        @fetchRolesHelper account, callback
-      else
-        console.error err
-        callback err

   fetchMembershipStatuses: secure (client, callback)->
     JAccount = require '../account'
@@ -1347,26 +1283,20 @@
   #     JPaymentGroup = require '../payment/group'
   #     JPaymentGroup.deletePlan this, data, callback

-  checkVmType: (data, callback) ->
-    unless data.type in ['user', 'group', 'expensed']
-      callback new KodingError "No such VM type: #{data.type}"
-    else
-      callback null

   fetchOrCountInvitations: permit 'send invitations',
     success: (client, type, method, options, callback)->
       return callback {message: "unimplemented feature"}

+
   fetchInvitationsByStatus: permit 'send invitations',
     success: (client, options, callback)->
       return callback {message: "unimplemented feature"}

-
   @each$ = (selector, options, callback)->
     selector.visibility = 'visible'
     @each selector, options, callback
-

   addSubscription$: permit 'edit own groups',


 Merge branch 'master.groups.pricing' into 'master.groups'

group: add subscription to group after successful debit

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -510,8 +510,9 @@

               subscription.debit debitOptions, (err) ->
                 return callback err  if err
-
-                callback null, group, subscription
+                group.addSubscription subscription, (err) ->
+                  return callback err  if err
+                  callback null, group, subscription

   @findSuggestions = (client, seed, options, callback)->
     {limit, blacklist, skip}  = options


Scripts: prefetching user vms on load

Scripts: prefetching user vms on load

--- workers/social/lib/social/render/scriptblock.coffee
+++ workers/social/lib/social/render/scriptblock.coffee
@@ -13,6 +13,7 @@
   campaignData     = null
   socialapidata    = null
   currentGroup     = null
+  userVMs          = null
   usePremiumBroker = no

   {bongoModels, client, slug} = options
@@ -26,9 +27,10 @@
     encodedFeed          = JSON.stringify prefetchedFeeds, replacer
     encodedCampaignData  = JSON.stringify campaignData, replacer
     encodedCustomPartial = JSON.stringify customPartial, replacer
-    encoedSocialApiData  = JSON.stringify socialapidata, replacer
+    encodedSocialApiData  = JSON.stringify socialapidata, replacer
     currentGroup         = JSON.stringify currentGroup
     userAccount          = JSON.stringify delegate
+    userVMs              = JSON.stringify userVMs

     usePremiumBroker = usePremiumBroker or options.client.context.group isnt "koding"

@@ -50,7 +52,8 @@
     <script>KD.config.usePremiumBroker=#{usePremiumBroker}</script>
     <script>KD.customPartial=#{encodedCustomPartial}</script>
     <script>KD.campaignData=#{encodedCampaignData}</script>
-    <script>KD.socialApiData=#{encoedSocialApiData}</script>
+    <script>KD.socialApiData=#{encodedSocialApiData}</script>
+    <script>KD.userVMs=#{userVMs}</script>
     <script src='/a/js/kd.libs.js?#{KONFIG.version}'></script>
     <script src='/a/js/kd.js?#{KONFIG.version}'></script>
     <script src='/a/js/koding.js?#{KONFIG.version}'></script>
@@ -134,7 +137,10 @@
             campaignData = campaignData_.data
           if group
             currentGroup = group
-          kallback()
+          bongoModels.JVM.fetchVmsByContext client, (err, vms) ->
+            console.log err  if err
+            userVMs = vms or []
+            kallback()


Popup check for its own behavior now so it can be shown without the overlay

--- lib/ecrire/app/assets/javascripts/shared/popup.js.coffee
+++ lib/ecrire/app/assets/javascripts/shared/popup.js.coffee
@@ -1,6 +1,19 @@
-ObserveJS.bind 'Posts.Popup', class
+ObserveJS.bind 'Popup', class
   loaded: =>
-    @on 'posts:index', @show
+    @on 'click', document, @clicked
+    @on 'keyup', document, @escaped

-  show: (e) =>
-    document.body.appendChild(e.HTML)
+  clicked: (e) =>
+    el = e.target
+    while el != @element() && el?
+      el = el.parentElement
+
+    unless el?
+      @remove()
+
+  escaped: (e) =>
+    if e.keyCode == 27
+      @remove()
+
+  remove: (e) =>
+    @element().remove()


use UNIX socket for staging/production environments

--- server/code/index.coffee
+++ server/code/index.coffee
@@ -1,4 +1,8 @@
+fs = require 'fs'
+path = require 'path'
+existsSync = fs.existsSync || path.existsSync
 crypto = require 'crypto'
+child_process = require 'child_process'

 express = require 'express'
 stylus = require 'stylus'
@@ -138,6 +142,11 @@

 # Define Port
 port = process.env.CU_PORT or 3001
+
+if existsSync(port) && fs.lstatSync(port).isSocket()
+  fs.chmodSync port, 0o600
+  child_process.exec "chown www-data #{port}"
+
 # Start Server
 app.listen port, ->
   console.log "Listening on #{port}\nPress CTRL-C to stop server."


Proper POST.

Proper POST.

--- scripts/screenshots.coffee
+++ scripts/screenshots.coffee
@@ -45,10 +45,8 @@
   callback_url = "#{brian_host}/brian/screenshot/#{room}"

   q = browsers: [browser_dict], url: url, callback_url:callback_url
-  console.log q, room
   msg.http(api_url)
-    .query(q)
     .auth(username, api_key)
-    .post() (err, res, body) ->
+    .post(QS.stringify(q)) (err, res, body) ->
       console.log res, body
       cb()


React to dropzone changes

React to dropzone changes

--- client/services/dropzoneservice.coffee
+++ client/services/dropzoneservice.coffee
@@ -23,12 +23,21 @@
   "maxfilesreached"
   "queuecomplete"
 ]
+mutatingDropzoneEvents = [
+  "addedfile"
+  "addedfiles"
+  "removedfile"
+]

 dropzoneLogger = new Logger("dropzone")
 logger = new Logger("DropzoneService")

 handleDropzoneEvent = (event, dropzone, args...) ->
   dropzoneLogger.debug("#{event}:", args)
+
+handleMutatingDropzoneEvent = (event, dropzone, args...) ->
+  dropzoneLogger.debug("Mutating event occurred: #{event}")
+  EditingService.onChange()

 b64ToBlob = (b64Data, contentType, sliceSize) ->
   sliceSize = sliceSize || 512
@@ -220,6 +229,8 @@
     })
     for event in monitoredDropzoneEvents
       dropzone.on(event, R.partial(handleDropzoneEvent, [event, dropzone]))
+    for event in mutatingDropzoneEvents
+      dropzone.on(event, R.partial(handleMutatingDropzoneEvent, [event, dropzone]))
     if existingFiles? && !R.isEmpty(existingFiles)
       description = if forPictures then "picture" else "file"
       picker = if forPictures then ((x) -> R.merge(x, {name: x.url})) else (


feat(draw-route): fix connecting endpoints of stencil elements

See #10.

--- app/scripts/stencils/base.coffee
+++ app/scripts/stencils/base.coffee
@@ -48,6 +48,7 @@

   _addEndpoints: (instance, toId, sourceAnchors, targetAnchors) ->
     console.log('add endpoints', arguments)
+    console.log(@sourceEndpoint, @targetEndpoint)
     i = 0
     while i < sourceAnchors.length
       sourceUUID = toId + sourceAnchors[i]
@@ -64,7 +65,7 @@
       j++
     return

-  @sourceEndpoint =
+  sourceEndpoint:
     # the definition of source endpoints (the small blue ones)
     endpoint: "Dot"
     paintStyle:
@@ -102,7 +103,7 @@
     ]]

-  @targetEndpoint =
+  targetEndpoint:
     # the definition of target endpoints (will appear when the user drags a connection)
     endpoint: "Dot"
     paintStyle:
@@ -127,20 +128,20 @@
     ]]

   # this is the paint style for the connecting lines..
-  @connectorPaintStyle =
+  connectorPaintStyle:
     lineWidth: 4
     strokeStyle: "#61B7CF"
     joinstyle: "round"
     outlineColor: "white"
     outlineWidth: 2

-  @connectorHoverStyle =
+  connectorHoverStyle:
     # .. and this is the hover style.
     lineWidth: 4
     strokeStyle: "#216477"
     outlineWidth: 2
     outlineColor: "white"

-  @endpointHoverStyle =
+  endpointHoverStyle:
     fillStyle: "#216477"
     strokeStyle: "#216477"


added password validation for resetPassword template

--- client/views/resetPassword/resetPassword.coffee
+++ client/views/resetPassword/resetPassword.coffee
@@ -9,6 +9,29 @@
   'submit #resetPassword': (event) ->
     event.preventDefault()
     password = $('input[type="password"]').val()
+
+    passwordErrors = do (password)->
+      errMsg = []
+      msg = false
+      if password.length < 7
+        errMsg.push i18n("error.minChar")
+      if password.search(/[a-z]/i) < 0
+        errMsg.push i18n("error.pwOneLetter")
+      if password.search(/[0-9]/) < 0
+        errMsg.push i18n("error.pwOneDigit")
+
+      if errMsg.length > 0
+        msg = ""
+        errMsg.forEach (e) ->
+          msg = msg.concat "#{e}\r\n"
+
+        Session.set 'entryError', msg
+        return true
+
+      return false
+
+    if passwordErrors then return
+
     Accounts.resetPassword Session.get('resetToken'), password, (error) ->
       if error
         Session.set('entryError', (error.reason || "Unknown error"))


Tests: Require fail overriding file.

Tests: Require fail overriding file.

--- client/test/lib/helpers/helpers.coffee
+++ client/test/lib/helpers/helpers.coffee
@@ -1,7 +1,9 @@
 utils    = require '../utils/utils.js'
+fail     = require '../utils/fail.js'
 register = require '../register/register.js'
 faker    = require 'faker'
 assert   = require 'assert'
+

 activitySelector = '[testpath=activity-list] section:nth-of-type(1) [testpath=ActivityListItemView]:first-child'


fix #76

fix #76

--- src/server/models/RebuildCache.coffee
+++ src/server/models/RebuildCache.coffee
@@ -67,21 +67,21 @@
       commonStats.sort((a, b)->
         averageA = a.rating / (a.gamesCitizen + a.gamesSheriff + a.gamesMafia + a.gamesDon)
         averageB = b.rating / (b.gamesCitizen + b.gamesSheriff + b.gamesMafia + b.gamesDon)
-        return if averageA > averageB then 1 else -1
+        return if averageA > averageB then -1 else 1
       )
-      top10 = if commonStats.length >= 10 then commonStats[0..10] else commonStats
+      top10 = if commonStats.length >= 10 then commonStats[0...10] else commonStats

-      fs.writeFile("#{__dirname}/../../static/json/common_stat_responce.json", JSON.stringify(commonStats), (err)->
+      fs.writeFile("#{__dirname}/../../static/json/common_stat_responce.json", JSON.stringify(commonStats, null, 2), (err)->
         if err
           done(err, null)
         else
-          fs.writeFile("#{__dirname}/../../static/json/top10.json", JSON.stringify(top10), (err)->
+          fs.writeFile("#{__dirname}/../../static/json/top10.json", JSON.stringify(top10, null, 2), (err)->
             if err
               done(err, null)
             else
               db.Player.all().success((players)->
                 fs.writeFile("#{__dirname}/../../static/json/players.json",
-                  JSON.stringify((player.name for player in players)), (err)->
+                  JSON.stringify((player.name for player in players), null, 2), (err)->
                   if err
                     done(err, null)
                   else


adding error message when saving is disabled

--- site/source/javascripts/code_sync/editor/views/document_manager.coffee
+++ site/source/javascripts/code_sync/editor/views/document_manager.coffee
@@ -64,8 +64,7 @@
     unless CodeSync.get("disableAssetOpen") is true
       container.append "
Open
"

-    unless CodeSync.get("disableAssetSave") is true
-      container.append "
Save
"
+    container.append "
Save
"

     @

@@ -135,7 +134,10 @@
       @$('.save-document').hide()

   saveDocument: ()->
-    @currentDocument.saveToDisk()
+    if CodeSync.get("disableAssetSave")
+      @editor.showStatusMessage(type: "error", message: "Saving is disabled in this demo.")
+    else
+      @currentDocument.saveToDisk()

   createDocument: ()->
     @openDocuments.add


don't clone via branch

don't clone via branch

--- server-ce/Gruntfile.coffee
+++ server-ce/Gruntfile.coffee
@@ -155,7 +155,6 @@
 			if !fs.existsSync(dir)
 				proc = spawn "git", [
 					"clone",
-					"-b", service.version,
 					repo_src,
 					dir
 				], stdio: "inherit"


GC Posts.insert fixed, post insert also requires refresh

--- server/GrandCentral.coffee
+++ server/GrandCentral.coffee
@@ -23,25 +23,7 @@
       }
       posts: {
         insert: =>
-          author_id = Meteor.call('get_user_id')
-          tron.test(gctest.insert_post, args[0], author_id)
-          #pick out only the appropriate fields
-          args[0] =
-            _.pick( args[0], 'content', 'parent_id', 'instance_id', '_id')
-          if (args[0].content == '')
-            error_list.push('blank content in post/insert')
-          args[0].author_id = author_id
-          args[0].votes = {
-            'up': {
-              users: []
-              weight: 0
-            }
-            'down': {
-              users: []
-              weight: 0
-            }
-          }
-          args[0].tags = []
+          args[0] = new ServerPost( args[0] )
         update: =>
           [selector, modifier] = args
           doc = Posts.findOne( selector._id )


use offset() instead of position() in linksHelper.offsetHash(), fixes issue #1578

--- src/scripts/helpers/links.coffee
+++ src/scripts/helpers/links.coffee
@@ -126,5 +126,5 @@
     offsetHash: () ->
       $pinnable = $('.pinnable')
       obscured = $pinnable.height() + 100
-      top = $(window.location.hash)?.position()?.top
+      top = $(window.location.hash)?.offset()?.top
       $(window).scrollTop(top - obscured) if top


Some validation logic

Some validation logic

--- src/scripts/scoreHandCtrl.coffee
+++ src/scripts/scoreHandCtrl.coffee
@@ -46,9 +46,14 @@
 		$scope.wasNormalGame = ->
 			not $scope.wasLeaster and not $scope.wasMisplay
 		$scope.canSubmitNormalGame = ->
-			_.filter($scope.players, (p) -> p.wasPicker).length is 1 and
-			_.filter($scope.players, (p) -> p.wasPartner).length is 1 and
-			_.filter($scope.players, (p) -> p.wasOut).length is 1
+			picker = _.filter $scope.players, (p) -> p.wasPicker
+			partner = _.filter $scope.players, (p) -> p.wasPartner
+			out = _.filter $scope.players, (p) -> p.wasOut
+			picker.length is 1 and
+			partner.length is 1 and
+			out.length is 1 and
+			picker[0] isnt out[0] and
+			partner[0] isnt out[0]
 		$scope.canSubmitLeaster = ->
 			_.filter($scope.players, (p) -> p.wasLeaster).length is 1
 		$scope.canSubmitMisplay = ->
@@ -56,4 +61,9 @@
 		$scope.canSubmit = ->
 			$scope.wasNormalGame() and $scope.canSubmitNormalGame() or
 			$scope.wasLeaster and $scope.canSubmitLeaster() or
-			$scope.wasMisplay and $scope.canSubmitMisplay()+			$scope.wasMisplay and $scope.canSubmitMisplay()
+
+		$scope.$watch 'wasLeaster', (wasLeaster) ->
+			$scope.wasMisplay = false if wasLeaster
+		$scope.$watch 'wasMisplay', (wasMisplay) ->
+			$scope.wasLeaster = false if wasMisplay


Update tutorial for #103

Update tutorial for #103

--- src/scripts/tutorialSteps.coffee
+++ src/scripts/tutorialSteps.coffee
@@ -102,7 +102,8 @@
       heading: 'Identifying species'
       content: [
         'We\'ve finished marking all the fish in this image.'
-        'Next we\'ll mark the seastars. Choose "seastar" from the species list.'
+        'Scallops and crustaceans are marked exactly the same as fish.'
+        'Seastars are a little different, so let\'s do one now. Choose "seastar" from the species list.'
       ]
       attach: x: 'right', to: '[value="seastar"]', at: x: 'left'
       style: width: 460


Bugfix

Bugfix

Needs refactoring

--- app/stores/classify-store.coffee
+++ app/stores/classify-store.coffee
@@ -15,11 +15,11 @@
   getInitialState: ->
     @data

-  getWorkflow: (project = null) ->
-    unless project
+  getWorkflow: (@project = null) ->
+    unless @project
       return throw new Error 'cannot fetch workflows for project'

-    project.get('workflows')
+    @project.get('workflows')
       .then ([workflow]) =>
         workflow.get('subject_sets').then ([subject_sets]) =>
           @getSubject(workflow, subject_sets.id)


remove log line from App

remove log line from App

--- app/templates/app/src/App.coffee
+++ app/templates/app/src/App.coffee
@@ -4,9 +4,6 @@

 # configure AngularJS
 App.config ($routeProvider)->
-
-    console.log 'App Configuration Started...'
-
     # setup routes
     $routeProvider
         .when '/',


Drop unused $window argument

Drop unused $window argument

--- src/scripts/wowser/ui/app.coffee
+++ src/scripts/wowser/ui/app.coffee
@@ -1,5 +1,5 @@
 class Wowser.ui.App

-  constructor: (@$scope, @$window) ->
+  constructor: (@$scope) ->
     @session = @$scope.session = new Wowser(Wowser.expansions.wotlk)
     @session.screen = 'authentication'


fix commas

fix commas

--- server/migrations.coffee
+++ server/migrations.coffee
@@ -18,7 +18,7 @@

 Migrations.add
   version: 2
-  name: 'Rename collection from neuropils to brain_regions',
+  name: 'Rename collection from neuropils to brain_regions'
   up: ->
     OLDNPILS = new Meteor.Collection("neuropils")

@@ -28,7 +28,7 @@

 Migrations.add
   version: 3
-  name: 'Rename field from neuropils to brain_regions',
+  name: 'Rename field from neuropils to brain_regions'
   up: ->
     for coll in [DriverLines, NeuronTypes]
       coll.find().forEach (doc) ->
@@ -46,7 +46,7 @@

 Migrations.add
   version: 4
-  name: 'Store S3 bucket name, region, key separately',
+  name: 'Store S3 bucket name, region, key separately'
   up: ->
     BinaryData.find().forEach (doc) ->
       parsed = parse_s3_url( doc.secure_url )


Use RocketChat Logger as SyncedCron logger

--- server/startup/cron.coffee
+++ server/startup/cron.coffee
@@ -1,5 +1,9 @@
 # Config and Start SyncedCron
+logger = new Logger 'SyncedCron'
+
 SyncedCron.config
+	logger: (opts) ->
+		logger[opts.level].call(logger, opts.message)
 	collectionName: 'rocketchat_cron_history'

 generateStatistics = ->


The message is now a label, but the fact that it's strong text forces it onto a second line

--- SingularityUI/app/views/taskOverviewSubview.coffee
+++ SingularityUI/app/views/taskOverviewSubview.coffee
@@ -72,7 +72,10 @@
                             vex.dialog.buttons.NO
                         ]
                         input: """
-                            <input name="wait-for-replacement-task" id="wait-for-replacement-task" type="checkbox" #{checked} /> Wait for replacement task to start before killing this task
+                            <label name="wait-for-replacement-task-label" id="wait-for-replacement-task-label" for="wait-for-replacement-task">
+                                <input name="wait-for-replacement-task" id="wait-for-replacement-task" type="checkbox" #{checked} />
+                                Wait for replacement task to start before killing this task
+                            </label>
                             <input name="message" type="text" placeholder="Message (optional)" />
                         """
                         message: templ id: @model.taskId


Add project page error boundary

Add project page error boundary

--- src/smc-webapp/project_page.cjsx
+++ src/smc-webapp/project_page.cjsx
@@ -308,6 +308,43 @@
         icon      : 'wrench'
         tooltip   : 'Project settings and controls'
         is_public : false
+
+ProjectError = rclass
+    displayName: 'Project-Error'
+
+    getInitialState: ->
+        error : undefined
+        info  : undefined
+
+    componentDidCatch: (error, info) ->
+        # TODO: Report the error to some backend service...
+        @setState
+            error : error
+            info  : info
+
+    render: ->
+        if @state.info?
+            <Alert
+                bsStyle = 'warning'
+                style   = {margin:'15px'}
+            >
+

+                    <Icon name='exclamation-triangle'/> Oh noes!! Well this is embarrasing... 😬
+

+

+                    {"We've already been notified of this error and are hard at work to fix it."}
+

+
+                    Trace
+

+                        {@state.error?.toString()}
+

+                        {@state.info.componentStack}
+

+
+            </Alert>
+        else
+            @props.children

 # Children must define their own padding from navbar and screen borders
 ProjectContentViewer = rclass
@@ -586,15 +623,17 @@
             <RamWarning project_id={@props.project_id} />
             <FreeProjectWarning project_id={@props.project_id} name={name} />
             {@render_file_tabs(group == 'public') if not @props.fullscreen}
-            <ProjectContentViewer
-                project_id      = {@props.project_id}
-                project_name    = {@props.name}
-                active_tab_name = {@props.active_project_tab}
-                opened_file     = {@props.open_files.getIn([active_path])}
-                file_path       = {active_path}
-                group           = {group}
-                save_scroll     = {@actions(name).get_scroll_saver_for(active_path)}
-            />
+            <ProjectError>
+                <ProjectContentViewer
+                    project_id      = {@props.project_id}
+                    project_name    = {@props.name}
+                    active_tab_name = {@props.active_project_tab}
+                    opened_file     = {@props.open_files.getIn([active_path])}
+                    file_path       = {active_path}
+                    group           = {group}
+                    save_scroll     = {@actions(name).get_scroll_saver_for(active_path)}
+                />
+            </ProjectError>

 exports.MobileProjectPage = rclass ({name}) ->
@@ -718,13 +757,15 @@
                     {@render_one_file_item() if @props.open_files_order.size == 1}

 if not @props.fullscreen}
-            <ProjectContentViewer
-                project_id      = {@props.project_id}
-                project_name    = {@props.name}
-                active_tab_name = {@props.active_project_tab}
-                opened_file     = {@props.open_files.getIn([active_path])}
-                file_path       = {active_path}
-                group           = {group}
-                save_scroll     = {@actions(name).get_scroll_saver_for(active_path)}
-            />
+            <ProjectError>
+                <ProjectContentViewer
+                    project_id      = {@props.project_id}
+                    project_name    = {@props.name}
+                    active_tab_name = {@props.active_project_tab}
+                    opened_file     = {@props.open_files.getIn([active_path])}
+                    file_path       = {active_path}
+                    group           = {group}
+                    save_scroll     = {@actions(name).get_scroll_saver_for(active_path)}
+                />
+            </ProjectError>


Improve setting update on `add`

Improve setting update on `add`

--- packages/rocketchat-lib/server/functions/settings.coffee
+++ packages/rocketchat-lib/server/functions/settings.coffee
@@ -6,7 +6,7 @@
 process.env.SETTINGS_HIDDEN?.split(',').forEach (settingId) ->
 	hiddenSettings[settingId] = 1

-RocketChat.settings._sorter = 0
+RocketChat.settings._sorter = {}

 ###
 # Add a setting
@@ -20,12 +20,13 @@
 	if not _id or not value?
 		return false

+	RocketChat.settings._sorter[options.group] ?= 0
+
 	options.packageValue = value
 	options.valueSource = 'packageValue'
-	options.ts = new Date
 	options.hidden = false
 	options.blocked = options.blocked || false
-	options.sorter ?= RocketChat.settings._sorter++
+	options.sorter ?= RocketChat.settings._sorter[options.group]++

 	if options.enableQuery?
 		options.enableQuery = JSON.stringify options.enableQuery
@@ -81,10 +82,15 @@
 		else
 			updateOperations.$setOnInsert.value = value

+	query = _.extend { _id: _id }, updateOperations.$set
+
 	if not options.section?
 		updateOperations.$unset = { section: 1 }
-
-	return RocketChat.models.Settings.upsert { _id: _id }, updateOperations
+		query.section = { $exists: false }
+
+	if not RocketChat.models.Settings.findOne(query)?
+		updateOperations.$set.ts = new Date
+		return RocketChat.models.Settings.upsert { _id: _id }, updateOperations


set line chart background to black and added haxis and vaxis textstyle color white

--- public/dreamcatcher/controllers/charts_controller.coffee
+++ public/dreamcatcher/controllers/charts_controller.coffee
@@ -27,9 +27,11 @@
     chart.draw(data, {
       title: "#{json.data['title']}",
       width: 600, height: 300,
-      backgroundColor: 'white',
-      legendTextStyle: {color: 'black'},
-      titleTextStyle: {color: 'black'}
+      backgroundColor: 'black',
+      legendTextStyle: {color: 'white'},
+      titleTextStyle: {color: 'white'},
+      hAxis: {textStyle: {color: 'white'}},
+      vAxis: {textStyle: {color: 'white'}}
     })


Remove outdated comment

Remove outdated comment

--- tapestry-core/src/main/coffeescript/META-INF/modules/core/forms.coffee
+++ tapestry-core/src/main/coffeescript/META-INF/modules/core/forms.coffee
@@ -102,7 +102,6 @@

         memo = error: false

-        # This will become more relevant shortly, when field validation is modernized:
         for field in this.find "[data-validation]"
           field.trigger events.field.inputValidation, memo


Prefix snippet parts to avoid repeated completion by accident.

--- settings/language-ledger.cson
+++ settings/language-ledger.cson
@@ -17,67 +17,67 @@
         suggestions: [
           {
             rightLabel: "Directive"
-            snippet: "account ${1:account}$0"
+            snippet: "account ${1:@account}$0"
             description: "Pre-declare valid account names"
             descriptionMoreURL: "http://www.ledger-cli.org/3.0/doc/ledger3.html#Command-Directives"
           }
           {
             rightLabel: "Directive"
-            snippet: "commodity ${1:commodity}$0"
+            snippet: "commodity ${1:@commodity}$0"
             description: "Pre-declare commodity names"
             descriptionMoreURL: "http://www.ledger-cli.org/3.0/doc/ledger3.html#Command-Directives"
           }
           {
             rightLabel: "Directive"
-            snippet: "alias ${1:alias}=${2:account}$0"
+            snippet: "alias ${1:@alias}=${2:@account}$0"
             description: "Define an alias for an account name"
             descriptionMoreURL: "http://www.ledger-cli.org/3.0/doc/ledger3.html#Command-Directives"
           }
           {
             rightLabel: "Directive"
-            snippet: "assert ${1:boolean}$0"
+            snippet: "assert ${1:@boolean}$0"
             description: "Throw an error if a condition is not met"
             descriptionMoreURL: "http://www.ledger-cli.org/3.0/doc/ledger3.html#Command-Directives"
           }
           {
             rightLabel: "Directive"
-            snippet: "bucket ${1:account}$0"
+            snippet: "bucket ${1:@account}$0"
             description: "Default account to use for balancing transactions"
             descriptionMoreURL: "http://www.ledger-cli.org/3.0/doc/ledger3.html#Command-Directives"
           }
           {
             rightLabel: "Directive"
-            snippet: "capture ${1:replacement} ${1:regexp}$0"
+            snippet: "capture ${1:@account} ${1:@regexp}$0"
             description: "Replace regexp'd account matches with another account"
             descriptionMoreURL: "http://www.ledger-cli.org/3.0/doc/ledger3.html#Command-Directives"
           }
           {
             rightLabel: "Directive"
-            snippet: "check ${1:boolean}$0"
+            snippet: "check ${1:@boolean}$0"
             description: "Issue a warning if a condition is not met"
             descriptionMoreURL: "http://www.ledger-cli.org/3.0/doc/ledger3.html#Command-Directives"
           }
           {
             rightLabel: "Directive"
-            snippet: "define ${1:variable}=${2:expression}$0"
+            snippet: "define ${1:@variable}=${2:@expression}$0"
             description: "Define value expressions"
             descriptionMoreURL: "http://www.ledger-cli.org/3.0/doc/ledger3.html#Command-Directives"
           }
           {
             rightLabel: "Directive"
-            snippet: "include ${1:path}$0"
+            snippet: "include ${1:@path}$0"
             description: "Include the stated file"
             descriptionMoreURL: "http://www.ledger-cli.org/3.0/doc/ledger3.html#Command-Directives"
           }
           {
             rightLabel: "Directive"
-            snippet: "tag ${1:string}$0"
+            snippet: "tag ${1:@tag}$0"
             description: "Pre-declare tag names"
             descriptionMoreURL: "http://www.ledger-cli.org/3.0/doc/ledger3.html#Command-Directives"
           }
           {
             rightLabel: "Directive"
-            snippet: "year ${1:year}$0"
+            snippet: "year ${1:@year}$0"
             description: "Pre-declare year used for dates without a year"
             descriptionMoreURL: "http://www.ledger-cli.org/3.0/doc/ledger3.html#Command-Directives"
           }


adding rmdir_rf and mkdir_p methods

adding rmdir_rf and mkdir_p methods

--- src/toaster/utils/fs-util.coffee
+++ src/toaster/utils/fs-util.coffee
@@ -1,10 +1,33 @@
-require 'fs'
+fs = require "fs"
+path = require "path"
+
+pn = path.normalize
+

 class FsUtil
 	@snapshots: {}

-	@find:(path, pattern, fn)->
-		exec "find #{path} -name '#{pattern}'", (error, stdout, stderr)=>
+
+	@rmdir_rf:(folderpath)->
+		files = fs.readdirSync folderpath
+		for file in files
+			file = "#{folderpath}/#{file}"
+			stats = fs.lstatSync file
+			if stats.isDirectory()
+				FsUtil.rmdir_rf file
+				fs.rmdirSync file
+			else
+				fs.unlinkSync file
+
+	@mkdir_p:(folderpath)->
+		folders = folderpath.split "/"
+		for folder, index in folders
+			continue if folder == ""
+			if !path.existsSync folder = folders.slice( 0, index + 1 ).join "/"
+				fs.mkdirSync folder, 0755
+
+	@find:(folderpath, pattern, fn)->
+		exec "find #{folderpath} -name '#{pattern}'", (error, stdout, stderr)=>
 			buffer = []
 			for item in items = stdout.trim().split "\n"
 				buffer.push item if item != "." && item != ".." && item != ""
@@ -17,66 +40,65 @@
 				buffer.push item if item != "." && item != ".." && item != ""
 			fn buffer

-	@watch_file:(path, onchange)->
-		path = pn path
-		onchange?( {type:"file", path:path, action:"watching"} )
+	@watch_file:(filepath, onchange)->
+		filepath = pn filepath
+		onchange?( {type:"file", path:filepath, action:"watching"} )

-		fs.watchFile path, {interval : 250}, (curr,prev)=>
+		fs.watchFile filepath, {interval : 250}, (curr,prev)=>
 			mtime = curr.mtime.valueOf() != prev.mtime.valueOf()
 			ctime = curr.ctime.valueOf() != prev.ctime.valueOf()
 			if mtime || ctime
-				onchange?( {type: "file", path: path, action: "updated"} )
+				onchange?( {type: "file", path:filepath, action: "updated"} )

-	@watch_folder:(path, onchange)->
-		path = pn path
-		onchange?( {type:"folder", path:path, action:"watching"} )
+	@watch_folder:(folderpath, onchange)->
+		folderpath = pn folderpath
+		onchange?( {type:"folder", path:folderpath, action:"watching"} )

-		exec "ls #{path}", (error, stdout, stderr)=>
+		exec "ls #{folderpath}", (error, stdout, stderr)=>

-			FsUtil.snapshots[path] = FsUtil.format_ls path, stdout
+			FsUtil.snapshots[folderpath] = FsUtil.format_ls folderpath, stdout

-			for item in FsUtil.snapshots[path]
+			for item in FsUtil.snapshots[folderpath]
 				if item.type == "folder"
 					FsUtil.watch_folder item.path, onchange
 				else
 					FsUtil.watch_file item.path, onchange

-		fs.watchFile path, {interval : 250}, (curr,prev)=>
+		fs.watchFile folderpath, {interval : 250}, (curr,prev)=>

-			exec "ls #{path}", (error, stdout, stderr)=>
+			exec "ls #{folderpath}", (error, stdout, stderr)=>

-				a = FsUtil.snapshots[path]
-				b = FsUtil.format_ls( path, stdout )
+				a = FsUtil.snapshots[folderpath]
+				b = FsUtil.format_ls( folderpath, stdout )
 				diff = ArrayUtil.diff a, b, "path"

 				for item in diff
 					info = item.item
 					info.action = item.action

-					switch info.action
+					if info.action == "created"
+						onchange?( info )

-						when "created"
-							onchange?( info )
-
-							if info.type == "file"
-								FsUtil.watch_file info.path, onchange
-							else if info.type == "folder"
-								FsUtil.watch_folder info.path, onchange
+						if info.type == "file"
+							FsUtil.watch_file info.path, onchange
+						else if info.type == "folder"
+							FsUtil.watch_folder info.path, onchange

-						when "deleted"
-							onchange?( info )
-							fs.unwatchFile item.path
+					else if info.action == "deleted"
+						onchange?( info )
+						fs.unwatchFile item.path
+
+				snapshot = FsUtil.format_ls folderpath, stdout
+				FsUtil.snapshots[folderpath] = snapshot

-				FsUtil.snapshots[path] = FsUtil.format_ls path, stdout
-
-	@format_ls:(path, stdout)->
+	@format_ls:(folderpath, stdout)->
 		list = stdout.toString().trim().split "\n"
 		for item, index in list
 			if item == "\n" || item == ""
 				list.splice index, 1
 			else
-				stats = fs.lstatSync "#{path}/#{item}"
+				stats = fs.lstatSync "#{folderpath}/#{item}"
 				list[index] =
 					type: if stats.isDirectory() then "folder" else "file"
-					path: "#{path}/#{item}"
+					path: "#{folderpath}/#{item}"
 		list


fix(apiPath): Will only fallback to other means of defining the apiPath if it's actually null, not if it's just an empty string.

--- coffee/factories/091_base_requester.tail.coffee
+++ coffee/factories/091_base_requester.tail.coffee
@@ -76,7 +76,7 @@

 		@$_actionStats = {}

-		@$_apiPath = @$_resourceClass::$_makeApiPath() if @$_resourceClass? and not @$_apiPath?.length
+		@$_apiPath = @$_resourceClass::$_makeApiPath() if @$_resourceClass? and not @$_apiPath?

 		@$_actionDefaults = angoolar.prototypallyExtendPropertyObject @, '$_actionDefaults'
 		@$_actions        = angoolar.prototypallyExtendPropertyObject @, '$_actions'
@@ -89,10 +89,10 @@
 			# Imply $_hasArray from isArray
 			actionDefinition.$_hasArray = actionDefinition.$_hasArray or actionDefinition.isArray or false

-			actionDefinition.$_apiPath = actionDefinition.$_resourceClass::$_makeApiPath() if actionDefinition.$_resourceClass? and not actionDefinition.$_apiPath?.length
+			actionDefinition.$_apiPath = actionDefinition.$_resourceClass::$_makeApiPath() if actionDefinition.$_resourceClass? and not actionDefinition.$_apiPath?

 			# Setup the url for the action if it has an $_apiPath
-			actionDefinition.url = "#{ @$_apiScheme }#{ angoolar.escapeColons @$_apiDomain }#{ actionDefinition.$_apiPath }" if actionDefinition.$_apiPath?.length
+			actionDefinition.url = "#{ @$_apiScheme }#{ angoolar.escapeColons @$_apiDomain }#{ actionDefinition.$_apiPath }" if actionDefinition.$_apiPath?

 			# Initialize each action's stats
 			@$_actionStats[ actionName ] = new angoolar.Stats @$injector


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/core_object.coffee
+++ test/core_object.coffee
@@ -50,6 +50,8 @@
     self.attrReader "test"

     test2: "cool"
+
+  M2

   o.include M, M2


change callback argument order in snapshot.test.coffee

--- workers/social/lib/social/models/computeproviders/snapshot.test.coffee
+++ workers/social/lib/social/models/computeproviders/snapshot.test.coffee
@@ -53,7 +53,7 @@

     it 'should fail to fetch snapshot data if user doesnt have permission', (done) ->

-      expectAccessDenied done, JSnapshot, 'one$', snapshotId = null
+      expectAccessDenied JSnapshot, 'one$', snapshotId = null, done

     it 'should be able to fetch the snapshot data', (done) ->
@@ -72,7 +72,7 @@

     it 'should fail to fetch snapshot data if user doesnt have permission', (done) ->

-      expectAccessDenied done, JSnapshot, 'some$', selector = {}, options = {}
+      expectAccessDenied JSnapshot, 'some$', selector = {}, options = {}, done

     it 'should be able to fetch snapshot data', (done) ->
@@ -93,7 +93,7 @@
     it 'should fail to rename snapshot if user doesnt have permission', (done) ->

       withConvertedUserAndSnapshot ({ snapshot }) ->
-        expectAccessDenied done, snapshot, 'rename', label = 'someLabel'
+        expectAccessDenied snapshot, 'rename', label = 'someLabel', done

     it 'should fail to rename snapshot if label is empty', (done) ->


JStackTemplate: Add connections field which will keep track of connections between nodes

--- workers/social/lib/social/models/computeproviders/stacktemplate.coffee
+++ workers/social/lib/social/models/computeproviders/stacktemplate.coffee
@@ -66,6 +66,8 @@
       machines        : [ Object ]
       extras          : [ Object ]

+      connections     : [ Object ]
+
       title           :
         type          : String
         required      : yes
@@ -105,6 +107,7 @@
         domains       : data.domains     ? []
         machines      : data.machines    ? []
         extras        : data.extras      ? []
+        connections   : data.connections ? []
         accessLevel   : data.accessLevel ? "private"
         group         : client.context.group
         originId      : delegate.getId()


JStackTemplate: generateStack: it generates JComputeStack from JStackTemplate instance

--- workers/social/lib/social/models/computeproviders/stacktemplate.coffee
+++ workers/social/lib/social/models/computeproviders/stacktemplate.coffee
@@ -7,7 +7,7 @@

   { permit }   = require '../group/permissionset'
   Validators   = require '../group/validators'
-  { revive }   = require './computeutils'
+  { revive, checkTemplateUsage } = require './computeutils'

   @trait __dirname, '../../traits/protected'

@@ -50,6 +50,8 @@
         update        :
           (signature Object, Function)
         clone         :
+          (signature Function)
+        generateStack :
           (signature Function)

     sharedEvents      :
@@ -288,6 +290,38 @@
     success: (client, accessLevel, callback) ->

       @update { $set: { accessLevel } }, callback
+
+
+  generateStack: permit
+
+    advanced: [
+      { permission: 'update own stack template', validateWith: Validators.own }
+      { permission: 'update stack template' }
+    ]
+
+    success: revive
+
+      shouldReviveClient   : yes
+      shouldReviveProvider : no
+
+    , (client, callback) ->
+
+      unless @getAt 'config.verified'
+        return callback new KodingError 'Stack is not verified yet'
+
+      { account, group, user } = client.r
+
+      ComputeProvider = require './computeprovider'
+
+      checkTemplateUsage this, account, (err) =>
+        return callback err  if err
+
+        account.addStackTemplate this, (err) =>
+
+          details = { account, user, group, client }
+          details.template = this
+
+          ComputeProvider.generateStackFromTemplate details, {}, callback

   update$: permit


JStackTemplate: return replacements only if there is no error

--- workers/social/lib/social/models/computeproviders/stacktemplate.coffee
+++ workers/social/lib/social/models/computeproviders/stacktemplate.coffee
@@ -234,7 +234,9 @@

       ComputeProvider = require './computeprovider'
       ComputeProvider.validateTemplateContent \
-        data.template, limitConfig, (err) -> callback err, replacements
+        data.template, limitConfig, (err) ->
+          return callback err  if err
+          callback null, replacements

   generateTemplateTitle = (provider) ->


Add local command echo

Add local command echo

--- assets/js/view_models/moo.coffee
+++ assets/js/view_models/moo.coffee
@@ -106,6 +106,7 @@
   sendCommand: ->
     command = @command()
     if command
+      @addLine c '\n'+command, 'gray'
       @history.unshift command
       if @history.length > @maxHistory()
         @history.pop()


frontend: Error when canceling ('esc') findMode

document.activeElement does not get set prior to

1 Entering find mode; and
2 Canceling ('esc') before being satisfied with keyword ('enter')

Signed-off-by: Carl Helmertz <d78a787955449ad50f586e5155db5f9260d297a8@gmail.com>

--- content_scripts/vimium_frontend.coffee
+++ content_scripts/vimium_frontend.coffee
@@ -660,7 +660,8 @@
   # instead. however, since the last focused element might not be the one currently pointed to by find (e.g.
   # the current one might be disabled and therefore unable to receive focus), we use the approximate
   # heuristic of checking that the last anchor node is an ancestor of our element.
-  if (findModeQueryHasResults && DomUtils.isSelectable(document.activeElement) &&
+  if (findModeQueryHasResults && document.activeElement &&
+      DomUtils.isSelectable(document.activeElement) &&
       isDOMDescendant(findModeAnchorNode, document.activeElement))
     DomUtils.simulateSelect(document.activeElement)
     # the element has already received focus via find(), so invoke insert mode manually


Test site_name

Test site_name

--- src/smc-webapp/customize.cjsx
+++ src/smc-webapp/customize.cjsx
@@ -62,7 +62,9 @@
         if @props.site_name
             {@props.site_name}
         else
-            <Loading/>
+

+                Is this broken?
+

 exports.SiteName = rclass
     displayName : 'SiteName-redux'


fix https://github.com/sagemathinc/smc/issues/1898 (hopefully) -- safari in private browsing: show_local_storage_warning broken

--- src/smc-webapp/feature.coffee
+++ src/smc-webapp/feature.coffee
@@ -18,15 +18,6 @@
 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 ###############################################################################
-
-if navigator?
-    # Check for cookies (see http://stackoverflow.com/questions/6125330/javascript-navigator-cookieenabled-browser-compatibility)
-    if not navigator.cookieEnabled
-        require('./smc-react').redux.getActions('page').show_cookie_warning()
-
-    # Check for local storage
-    if not require('smc-util/misc').has_local_storage()
-        require('./smc-react').redux.getActions('page').show_local_storage_warning()

 ####################################################
 #
@@ -92,3 +83,16 @@
 # You can use DEBUG anywhere in the webapp code!
 if DEBUG
     console.log "DEBUG MODE:", DEBUG
+
+# These checks must be **after** the above functions are defined, since showing
+# these warnings requires functions like get_browser are already defined.
+# See https://github.com/sagemathinc/smc/issues/1898
+if navigator?
+    # Check for cookies (see http://stackoverflow.com/questions/6125330/javascript-navigator-cookieenabled-browser-compatibility)
+    if not navigator.cookieEnabled
+        require('./smc-react').redux.getActions('page').show_cookie_warning()
+
+    # Check for local storage
+    if not require('smc-util/misc').has_local_storage()
+        require('./smc-react').redux.getActions('page').show_local_storage_warning()
+


Removing provider fixed (Wrong params, http://mcavage.me/node-restify/)

--- src/steroids/Providers.coffee
+++ src/steroids/Providers.coffee
@@ -115,7 +115,7 @@
     @getProviderByName(provider_name).then (provider) =>

       console.log "Removing provider #{provider_name}..."
-      @config_api.del("/app/#{@getAppId()}/service_providers/#{provider}.json", data, (err, req, res, obj) =>
+      @config_api.del("/app/#{@getAppId()}/service_providers/#{provider}.json", (err, req, res, obj) =>
         console.log 'done'
         @config_api.close()
       )