Remove useless double quotes

Remove useless double quotes

--- backend/init.coffee
+++ backend/init.coffee
@@ -34,15 +34,15 @@
   )

 if !atom.config.get('integrated-learn-environment.oauthToken')
-  oauthPrompt = document.createElement "div"
+  oauthPrompt = document.createElement 'div'
   oauthPrompt.setAttribute 'style', 'width:100%; text-align: center;'

-  oauthLabel = document.createElement "div"
+  oauthLabel = document.createElement 'div'
   oauthLabel.setAttribute 'style', 'margin-top: 12px; font-weight: bold; font-size: 12px;'
   oauthLabel.appendChild document.createTextNode 'Please enter your Learn OAuth Token'
-  tokenLinkDiv = document.createElement "div"
+  tokenLinkDiv = document.createElement 'div'
   tokenText = document.createTextNode 'Get your token here: '
-  tokenLink = document.createElement "a"
+  tokenLink = document.createElement 'a'
   tokenLink.title = 'https://learn.co/ile/token'
   tokenLink.href = 'https://learn.co/ile/token'
   tokenLink.setAttribute 'style', 'text-decoration: underline;'


Update title

Update title

--- src/components/navbar/server-error-monitoring.cjsx
+++ src/components/navbar/server-error-monitoring.cjsx
@@ -98,7 +98,7 @@
       router.transitionTo('viewStudentDashboard', {courseId})
       Dialog.hide()
     dialog:
-      title: 'Unable to practice topic'
+      title: 'No exercises are available'
       body: <NoExercisesMessage />
       buttons: [
         <BS.Button key='ok' onClick={hideDialog} bsStyle='primary'>OK</BS.Button>


removed buffer logic from string axes

removed buffer logic from string axes

--- src/viz/types/helpers/graph/includes/buffer.coffee
+++ src/viz/types/helpers/graph/includes/buffer.coffee
@@ -119,15 +119,18 @@
         zero = 0
         domain = domain.slice().reverse() if axis is "y"

+        strings = domain.filter (d) -> d.constructor is String
         additional = Math.abs(domain[1] - domain[0]) * 0.05 or 1

-        domain[0] = domain[0] - additional
-        domain[1] = domain[1] + additional
+        unless strings.length

-        domain[0] = zero if (allPositive and domain[0] < zero) or
-                            (allNegative and domain[0] > zero)
-        domain[1] = zero if (allPositive and domain[1] < zero) or
-                            (allNegative and domain[1] > zero)
+          domain[0] = domain[0] - additional
+          domain[1] = domain[1] + additional
+
+          domain[0] = zero if (allPositive and domain[0] < zero) or
+                              (allNegative and domain[0] > zero)
+          domain[1] = zero if (allPositive and domain[1] < zero) or
+                              (allNegative and domain[1] > zero)

         domain = domain.reverse() if axis is "y"


Added Manifest Loader

Added Manifest Loader

Since all three current commands load the manifest in the same manner
we added a function to share the validation/etc code.

--- bin/kdc-plus.coffee
+++ bin/kdc-plus.coffee
@@ -22,14 +22,12 @@

-# ## Compile
-#
-# The function called when `kdc-plus compile` is used.
-compile = (appPath, unknownArgs..., opts={}, log=console.error) ->
-  if typeof appPath is 'object' then [opts, appPath] = [appPath, undefined]
-
-  appPath ?= process.cwd()
-  appPath = path.resolve appPath
+# ## Load Manifest
+#
+# Since many of the commands load the manifest, and have to deal with
+# failures/etc in the same manner, this is a little convenience function
+# which handles the grunt work and returns the manifest.
+_loadManifest = (appPath, callback) ->
   loadOpts =
     validate: true

@@ -38,39 +36,53 @@
       log "Error Loading Manifest: #{err.message}"
       return process.exit 1

-    # ## CLI & Manifest Defaults
-    # Now that we have a manifest loaded, assign our defaults, letting the
-    # CLI opts override everything.
-
-    # If they declared pipe and file, warn that file is ignored.
-    if opts.file? and opts.pipe
-      log 'Warning: --pipe and --file are both defined, file will be ignored.'
-
-    # Since pipe desn't make sense as a manifest option, lets warn it.
-    if manifest.pipe is true
-      log 'Warning: "pipe" is not supported in the manifest'
-
-    opts.bare ?= false
-
-    if opts.file? or manifest.file?
-      opts.file = opts.file ? manifest.file
-    else
-      opts.file = 'index.js'
-
-    if opts.commonjs? or manifest.commonjs?
-      opts.commonjs = opts.commonjs ? manifest.commonjs
-    else
-      opts.commonjs = false
-
-    if opts.coffee? or manifest.coffee?
-      opts.coffee = opts.coffee ? manifest.coffee
-    else
-      opts.coffee = false
-
     if vwarns? then log warning for warning in vwarns
     if vfails?
       log failure for failure in vfails
       return process.exit 1
+
+    callback manifest
+
+
+
+# ## Compile
+#
+# The function called when `kdc-plus compile` is used.
+compile = (appPath, unknownArgs..., opts={}, log=console.error) ->
+  if typeof appPath is 'object' then [opts, appPath] = [appPath, undefined]
+
+  appPath ?= process.cwd()
+  appPath = path.resolve appPath
+
+  _loadManifest appPath, (manifest) ->
+    # ## CLI & Manifest Defaults
+    # Now that we have a manifest loaded, assign our defaults, letting the
+    # CLI opts override everything.
+
+    # If they declared pipe and file, warn that file is ignored.
+    if opts.file? and opts.pipe
+      log 'Warning: --pipe and --file are both defined, file will be ignored.'
+
+    # Since pipe desn't make sense as a manifest option, lets warn it.
+    if manifest.pipe is true
+      log 'Warning: "pipe" is not supported in the manifest'
+
+    opts.bare ?= false
+
+    if opts.file? or manifest.file?
+      opts.file = opts.file ? manifest.file
+    else
+      opts.file = 'index.js'
+
+    if opts.commonjs? or manifest.commonjs?
+      opts.commonjs = opts.commonjs ? manifest.commonjs
+    else
+      opts.commonjs = false
+
+    if opts.coffee? or manifest.coffee?
+      opts.coffee = opts.coffee ? manifest.coffee
+    else
+      opts.coffee = false

     # Take all of our files and make them relative to our appPath if
     # they are relative.
@@ -209,9 +221,14 @@
 # corrisponding dependency files *(package.json, bower.json, etc)*.
 install = (appPath, unknownArgs..., opts={}, log=console.error) ->
   if typeof appPath is 'object' then [opts, appPath] = [appPath, undefined]
-  log 'Error: Not Implemented'
-  process.exit 1
-
+  log 'Not Implemented'
+  return process.exit 1
+
+  appPath ?= process.cwd()
+  appPath = path.resolve appPath
+
+  _loadManifest appPath, (manifest) ->
+

 # ## Outdated
@@ -220,8 +237,14 @@
 # also the corrisponding dependency files *(package.json, bower.json, etc)*.
 outdated = (appPath, unknownArgs..., opts={}, log=console.error) ->
   if typeof appPath is 'object' then [opts, appPath] = [appPath, undefined]
-  log 'Error: Not Implemented'
-  process.exit 1
+  log 'Not Implemented'
+  return process.exit 1
+
+  appPath ?= process.cwd()
+  appPath = path.resolve appPath
+
+  _loadManifest appPath, (manifest) ->
+


Fix runtime error range detection within threads and repeated error types in messages

--- app/lib/aether/problems.coffee
+++ app/lib/aether/problems.coffee
@@ -263,14 +263,14 @@
 extractRuntimeErrorDetails = (options) ->
   if error = options.error
     options.kind ?= error.name  # I think this will pick up [Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError, DOMException]
-
-    if options.aether.options.useInterpreter
-      options.message = error.toString()
-    else
-      options.message = error.message or error.toString()
+    options.message = error.message or error.toString()
     options.hint = error.hint or getRuntimeHint options
     options.level ?= error.level
     options.userInfo ?= error.userInfo
+    if error.range and not options.range
+      if _.isNumber(error.range[0]) and _.isNumber(error.range[1]) and error.range[0] < options.aether.raw.length  # Lua doesn't have good range info for some reason
+        range = ranges.offsetsToRange(error.range[0], error.range[1], options.aether.raw or '')
+        options.range = [range.start, range.end]  # We expect array instead of object in options.range below

   # NOTE: lastStatementRange set via instrumentation.logStatementStart(originalNode.originalRange)
   options.range ?= options.aether?.lastStatementRange
@@ -278,7 +278,7 @@
     loc = options.aether?.esperEngine?.evaluator?.topFrame?.ast?.loc
     options.range ?= esperLocToAetherLoc loc

-  if options.error?.name?
+  if options.error?.name? and not ///^#{options.error.name}///.test options.message
     options.message = "#{options.error.name}: #{options.message}"

   if options.range?


Improve plot title

Improve plot title

--- app/scripts/controllers.coffee
+++ app/scripts/controllers.coffee
@@ -125,7 +125,7 @@

     $scope.plots = [
-      {id:"statusSummary", title: "Functioning Waterpoints"},
+      {id:"statusSummary", title: "Waterpoint status (by % Functional)"},
       {id:"percFunLeaders", title: "Leaderboard: Percentage Functional"},
       {id:"popReach", title: "Leaderboard: % Population Served"}]


Keep loading further waterpoints in map view

Currently disabled for performance reasons.

--- app/scripts/controllers.coffee
+++ app/scripts/controllers.coffee
@@ -4,16 +4,22 @@
   .controller 'MainCtrl', ($scope, Waterpoint) ->
     Waterpoint.query (waterpoints) ->
       $scope.waterpoints = waterpoints._items
-  .controller 'MapCtrl', ($scope, Waterpoint) ->
+  .controller 'MapCtrl', ($http, $scope, Waterpoint) ->
     $scope.dar =
       lat: -6.7701973
       lng: 39.2664484
       zoom: 6
     $scope.markers = {}
-    Waterpoint.query (waterpoints) ->
+    addMarkers = (waterpoints) ->
       for p in waterpoints._items
         $scope.markers[p._id] =
           group: p.district
           lat: p.latitude
           lng: p.longitude
-          message: "#{p.wpt_code}\nStatus: #{p.status}"
+          message: "#{p.wpt_code}<br />Status: #{p.status}"
+      # This would keep loading further waterpoints as long as there are any.
+      # Disabled for performance reasons
+      # if waterpoints._links.next
+      #   $http.get(waterpoints._links.next.href)
+      #     .success addMarkers
+    Waterpoint.query addMarkers


Add missing event handler

Add missing event handler

--- app/views/projects_item.coffee
+++ app/views/projects_item.coffee
@@ -38,6 +38,10 @@
   deployProject: =>
     @model.deploy()

+  showDeploys: =>
+    @deploys.loadCollection().done =>
+      @$('.view-project').append @deploys.render().el
+
   updateProject: (e) =>
     e.preventDefault()
     @model.save


very slightly improved li help

very slightly improved li help

--- code/li.coffee
+++ code/li.coffee
@@ -12,15 +12,15 @@
   """
   Usage: li <command> [OPTIONS]
     Commands:
-      list      List instances
-      create    Create a instance by config
-      destroy   Destroy an instance
+      list                      List instances
+      create <config-name>      Create a instance by config
+      destroy <instance-name>   Destroy an instance

-      start     Start an instance
-      stop      Stop an instance
-      restart   Restart an instance
-      sh        Execute a command on an instance
-      deploy    Run deployment hooks on an instance [OBSOLESCENT]
+      start <instance-name>     Start an instance
+      stop <instance-name>      Stop an instance
+      restart <instance-name>   Restart an instance
+      sh <instance-name> <cmd>  Execute a command on an instance
+      deploy <instance-name>    Run deployment hooks on an instance [OBSOLESCENT]

   """
   process.stdout.write help


Deleted unnecessary event when friending the bot

--- bot.iced
+++ bot.iced
@@ -360,7 +360,7 @@
 			bot.sendMessage chatterID, "I couldn't understand your request. Send me '+help' for a list of available commands and functions."

 bot.on "friend", (steamID, Relationship) ->
-	if Relationship is Steam.EFriendRelationship.RequestRecipient or Relationship is Steam.EFriendRelationship.RequestInitiator
+	if Relationship is Steam.EFriendRelationship.RequestRecipient
 		# Remove people who try to friend the bot after notifying them
 		bot.addFriend steamID
 		setTimeout ->


Working...

Working...

--- core.coffee
+++ core.coffee
@@ -37,15 +37,13 @@
 				items = JSON.parse(body).items
 				async.each items, (item, callback) ->
 					db.Repository.findOneAndUpdate {fullName: item.full_name},
-						$setOnInsert:
-							fullName: item.full_name
-						$set:
-							stars: item.stargazers_count
-							forks: item.forks_count
+						fullName: item.full_name
+						stars: item.stargazers_count
+						forks: item.forks_count
 					, upsert: true, new: true
 					, (err, repo) ->
 						fetchRepoLanguages repo
-						# fetchRepoCommits repo
+						fetchRepoCommits repo
 						callback()
 				, ->
 					console.log "Saved #{items.length} repositories of Top Repositories from page #{page}"
@@ -64,50 +62,64 @@
 				console.log "Saved language statistics for repo #{repo.fullName}"

 fetchRepoCommits = (repo, date = "") ->
+	console.log "Adding API Job to fetch commits for repo #{repo.fullName}"
 	apiJobs.push ->
-		githubApi
-			url: "/repos/#{repo.full_name}/commits"
+		console.log "Fetching commits for repo #{repo.fullName}"
+		githubApi opts =
+			url: "/repos/#{repo.fullName}/commits"
 			qs:
 				until: date
 				per_page: 100
 		, (err, response, body) ->
 			items = JSON.parse(body)
 			if items.length > 0
-				items.forEach (item) ->
+				async.each items, (item, callback) ->
 					date = item.commit.author.date
+					return callback() unless item.author?
 					getUserOrCreateUser item.author.login, (err, user) ->
-						commit = new db.Commit
+						db.Commit.findOneAndUpdate {sha: item.sha},
 							sha: item.sha
 							author: user._id
 							repository: repo._id
 							timestamp: item.commit.author.date
-						commit.save(err, commit) ->
-							if not err?
-								exports.fetchCommit repo, commit
-				fetchRepoCommits repo, date
+						, upsert: true, new: true
+						, (err, commit) ->
+							fetchCommit repo, commit unless err?
+							callback()
+				, ->
+					console.log "Saved commits for repo #{repo.fullName}"
+					fetchRepoCommits repo, date

 getUserOrCreateUser = (username, callback) ->
-	db.User.findOneAndUpdate {username: username}, {$setOnInsert: username: username}, upsert: true, new: true, (err, user) ->
+	db.User.findOneAndUpdate {username: username}, {username: username}, upsert: true, new: true, (err, user) ->
 		callback err, user

 fetchCommit = (repo, commit) ->
+	console.log "Adding API Job to fetch commit #{commit.sha}"
 	apiJobs.push ->
+		console.log "Fetching commit #{commit.sha}"
 		githubApi
 			url: "/repos/#{repo.fullName}/commits/#{commit.sha}"
 		, (err, response, body) ->
 			changes = []
-			com = JSON.parse(body)
+			com = undefined
+			try
+				com = JSON.parse(body)
+			catch e
+				return console.error "Invalid JSON: ", body
 			items = com.files
-			items.forEach (item) ->
-				fileName = item.fileName
+			async.each items, (item, callback) ->
+				fileName = item.filename
 				changesMade = item.changes
-				language = mapper.getLanguage fileName.substring fileName.lastIndexOf(".")
-				db.Commit.findAndModify {sha: item.sha}, {$addToSet: {changes: {language: language}}}, {new:true} (err, resp) ->
-					if !err
-						db.Commit.findAndModify {sha: item.sha, "changes.language": language }, {$inc: {"changes.$.count": changesMade}}, {new: true} , (err, resp) ->
-							if !err
-								console.log "Successfully inserted commit"
+				language = mapper.getLanguage fileName.substring fileName.lastIndexOf "."
+				db.Commit.findOneAndUpdate {_id: commit._id}, {$addToSet: {changes: {language: language}}}, new: true, (err, resp) ->
+					return callback err if err?
+					db.Commit.findOneAndUpdate {_id: commit._id, "changes.language": language }, {$inc: {"changes.$.count": changesMade}}, new: true, (err, resp) ->
+						return callback err if err?
+						callback()
+			, ->
+				console.log "Saved commit #{commit.sha}"

 exports.startJobs = ->
 	intervalDescriptor = null
@@ -116,5 +128,6 @@
 		clearInterval intervalDescriptor if searchJobs.length is 0 and apiJobs.length is 0
 		searchJobs.shift()?() for i in [0 ... globals.searchRequestsPerMinute]
 		apiJobs.shift()?() for i in [0 ... globals.apiRequestsPerMinute]
+		undefined
 	intervalDescriptor = setInterval doTask, 1000 * 60 # Runs every minute
 	doTask()


Build: build paths and ways changed for landing

--- build-client.coffee
+++ build-client.coffee
@@ -124,7 +124,7 @@
           queue.next()
       ,
       ->
-        exec 'cd client/landing && gulp build', (err, stdout, stderr)->
+        exec 'cd client/landing && gulp build --site=site.landing', (err, stdout, stderr)->
           if err
           then console.error err
           else console.log """
@@ -134,13 +134,32 @@
           queue.next()
       ,
       ->
-        exec "rsync -av #{__dirname}/client/landing/static/a/out/ #{__dirname}/website/a/out/", (err, stdout, stderr)->
+        exec "rsync -av #{__dirname}/client/landing/static/a/site.landing/ #{__dirname}/website/a/site.landing/", (err, stdout, stderr)->
           if err
           then console.error err
           else console.log "# LANDING PAGE EXPORTED"
           queue.next()
       ->
           console.log "\n# EXTERNAL BUILDS FINISHED!\n"
+          console.log """
+
+            ########################################
+            ########################################
+
+            # FOR LANDING PAGE DEVELOPMENT PLEASE DO
+
+            # for a build and watch system
+            $ cd #{__dirname}/client/landing/site.landing/
+            $ gulp --site=site.landing --outputDir=#{__dirname}/website/a/site.landing/
+
+            # for just build and quit
+            $ cd #{__dirname}/client/landing/site.landing/
+            $ gulp build --site=site.landing --outputDir=#{__dirname}/website/a/site.landing/
+
+            ########################################
+            ########################################
+
+            """
           queue.next()
     ]


Renaming --maps option to --map for consistency.

--- command.coffee
+++ command.coffee
@@ -39,7 +39,7 @@
   ['-i', '--interactive',     'run an interactive CoffeeScript REPL']
   ['-j', '--join [FILE]',     'concatenate the source CoffeeScript before compiling']
   ['-l', '--lint',            'pipe the compiled JavaScript through JavaScript Lint']
-  ['-m', '--maps',            'generate source map and save as .map files']
+  ['-m', '--map',             'generate source map and save as .map files']
   ['-n', '--nodes',           'print out the parse tree that the parser produces']
   [      '--nodejs [ARGS]',   'pass options directly to the "node" binary']
   ['-o', '--output [DIR]',    'set the output directory for compiled JavaScript']


more debug messages

more debug messages

--- command.coffee
+++ command.coffee
@@ -42,7 +42,7 @@
     return query

   update: (deployment, callback) =>
-    debug 'updating....'
+    debug 'updating with....', deployment
     uri = url.format
       protocol: 'http'
       host: @destinationElasticsearchUrl
@@ -64,6 +64,7 @@
     }, callback)

   normalize: (result) =>
+    debug 'got result', result
     buckets = result.aggregations.addGatebluDevice.group_by_deploymentUuid.buckets
     _.map buckets, (bucket) =>
       {


more updates to the testg

more updates to the testg

--- apps/show/test/template.coffee
+++ apps/show/test/template.coffee
@@ -109,9 +109,6 @@
   describe 'with a private profile', ->

     beforeEach ->
-      sd =
-        ARTSY_URL : 'http://localhost:5000'
-        ASSET_PATH: 'http://localhost:5000'
       @show = new PartnerShow fabricate('show')
       @show.partner().default_profile_public = false
       @html = render('template')({


detects error if game files don't exist

detects error if game files don't exist

--- platform/src/app.coffee
+++ platform/src/app.coffee
@@ -27,6 +27,15 @@
 EVAL_ASSETS =
   underscore: "lib/underscore/underscore.js"
   gamEvolveCommon: "gamEvolveCommon.js"
+
+EDITOR_URL_PAIRS = [
+  ["modelEditor", "model.json"]
+  ["assetsEditor", "assets.json"]
+  ["actionsEditor", "actions.js"]
+  ["toolsEditor", "tools.js"]
+  ["layoutEditor", "layout.json"]
+  ["servicesEditor", "services.json"]
+]

 ### Globals ###
@@ -192,7 +201,7 @@
   return editor

 loadIntoEditor = (editor, url) ->
-  $.ajax
+  return $.ajax
     url: url
     dataType: "text"
     cache: false
@@ -340,10 +349,7 @@
   requestAnimationFrame(handleAnimation)

 # Saves all editor contents to LocalStorage
-saveCodeToCache = ->
-  programId = window.location.hash.slice(1)
-  if !programId then throw new Error("No program ID to save")
-
+saveCodeToCache = (programId) ->
   # TODO: should check that currentFrame == 0 before saving model
   codeToCache = {}
   for id, editor of editors
@@ -354,11 +360,7 @@

 # Loads all editor contents from LocalStorage as JSON and returns it
 # If nothing was stored, returns null
-loadCodeFromCache = ->
-  programId = window.location.hash.slice(1)
-  if !programId then throw new Error("No program ID to load")
-
-  return localStorage.getItem(programId)
+loadCodeFromCache = (programId) -> return localStorage.getItem(programId)

 # Sets cached code in the editors
 setCodeFromCache = (cachedCodeJson) ->
@@ -370,11 +372,7 @@
     editor.selection.clearSelection()

 # Remove code in LocalStorage
-clearCodeInCache = ->
-  programId = window.location.hash.slice(1)
-  if !programId then throw new Error("No program ID to remove")
-
-  localStorage.removeItem(programId)
+clearCodeInCache = (programId) -> localStorage.removeItem(programId)

 # Add the factory so it can be used later
 registerService = (name, factory) -> services[name] = factory
@@ -402,6 +400,8 @@
   setupLayout()
   setupButtonHandlers()

+  $(window).on "onresize", handleResize
+
   # Create all the JSON and JS editors
   for id in ["modelEditor", "assetsEditor", "actionsEditor", "toolsEditor", "layoutEditor", "servicesEditor"]
     editors[id] = setupEditor(id, "ace/mode/javascript")
@@ -412,43 +412,34 @@
   resetLogContent()

   # A hash needs to be set, or we won't be able to load the code
-  if not window.location.hash  then window.location.hash = "optics"
+  if not window.location.search then window.location.search = "?optics/"
+  programId = window.location.search.slice(1)

   # Offer to load code from the cache if we can
   loadedCode = false
-  cachedCodeJson = loadCodeFromCache()
+  cachedCodeJson = loadCodeFromCache(programId)
   if cachedCodeJson
     if window.confirm("You had made changes to this code. Should we load your last version?")
       setCodeFromCache(cachedCodeJson)
       loadedCode = true
     else
-      clearCodeInCache()
-
-  # Otherwise just load from the default "optics" directory
+      clearCodeInCache(programId)
+
+  # Otherwise just load from a local directory
   if not loadedCode
-    for [id, url] in [["modelEditor", "optics/model.json"],
-                      ["assetsEditor", "optics/assets.json"],
-                      ["actionsEditor", "optics/actions.js"],
-                      ["toolsEditor", "optics/tools.js"],
-                      ["layoutEditor", "optics/layout.json"],
-                      ["servicesEditor", "optics/services.json"]]
-      loadIntoEditor(editors[id], url)
-
-  for id in ["modelEditor", "assetsEditor", "actionsEditor", "toolsEditor", "layoutEditor", "servicesEditor"]
-    editors[id].getSession().on "change", -> notifyCodeChange()
-
-  # TODO: find another way to include global data
-  globals.editors = editors
-
-  # Setup event handlers
-  $(window).on "onresize", handleResize
-
-  # Load common assets
-  GE.loadAssets EVAL_ASSETS, (err, loadedAssets) ->
-    if err then return showMessage(MessageType.Error, "Cannot load common assets")
-
-    # Make this globally available
-    evalLoadedAssets = (script for name, script of loadedAssets)
-    # Load code
-    notifyCodeChange()
-
+    ajaxRequests = (loadIntoEditor(editors[id], "#{programId}#{url}") for [id, url] in EDITOR_URL_PAIRS)
+    $.when(ajaxRequests...).fail(-> showMessage(MessageType.Error, "Cannot load game files")).then ->
+      # Load common assets
+      GE.loadAssets EVAL_ASSETS, (err, loadedAssets) ->
+        if err then return showMessage(MessageType.Error, "Cannot load common assets")
+
+        # Make this globally available
+        evalLoadedAssets = (script for name, script of loadedAssets)
+
+        # Setup event handlers on code change
+        for id in ["modelEditor", "assetsEditor", "actionsEditor", "toolsEditor", "layoutEditor", "servicesEditor"]
+          editors[id].getSession().on "change", -> notifyCodeChange()
+
+        # Load code
+        notifyCodeChange()
+


better error on save failure

better error on save failure

--- client/driver_lines.coffee
+++ client/driver_lines.coffee
@@ -127,6 +127,7 @@
   if error?
     # FIXME: be more useful. E.g. hide a "saving... popup"
     console.error "driver_line_insert_callback with error:", error
+    alert "Saving failed: "+error
   return

 # @remove_driver_line is defined in ../neuron-catalog.coffee


Using Coffeescript slice syntax to avoid pushing into revJournal

--- client/lib/revision.coffee
+++ client/lib/revision.coffee
@@ -8,9 +8,8 @@
   journal = data.journal
   revTitle = data.title
   revStory = []
-  revJournal = []
-  for journalEntry in journal.slice 0, (+revIndex)+1
-    revJournal.push(journalEntry)
+  revJournal = journal[0..(+revIndex)]
+  for journalEntry in revJournal
     revStoryIds = revStory.map (storyItem) -> storyItem.id
     switch journalEntry.type
       when 'create'


Only list view can be missing.

Only list view can be missing.

--- client/views-record.coffee
+++ client/views-record.coffee
@@ -5,8 +5,6 @@
   span().text m if m = (expand_type field).measure

 @VIEWS_RECORD =
-  fieldview:   -> div().text "Missing field template."
-  record:      -> div().text "Missing record template."
   list:        -> div().text "Missing list template."
   field_label: field_label
   measure:     measure


only sign in once

only sign in once

--- assets/scripts/app/auth.coffee
+++ assets/scripts/app/auth.coffee
@@ -16,7 +16,11 @@
   # for auto signin then we're trying to sign in.
   autoSignIn: (path) ->
     console.log 'autoSignIn'
-    if user = sessionStorage.getItem('travis.user')
+    global  = localStorage.getItem('travis.user')
+    session = sessionStorage.getItem('travis.user')
+    user    = session || global
+    if user
+      localStorage.setItem('travis.user', user) unless global
       data = JSON.parse(user)
       data = { user: data } unless data.user?
       @setData(data)
@@ -34,6 +38,7 @@
   signOut: ->
     localStorage.removeItem('travis.auto_signin')
     localStorage.removeItem('travis.locale')
+    localStorage.removeItem('travis.user')
     sessionStorage.clear()
     @setData()


Fixed cancel button state in importing. Fixes #591

--- client/import.coffee
+++ client/import.coffee
@@ -1,5 +1,5 @@
 # Local (client-only) document of importing files
-class ImportingFile extends BaseDocument
+class @ImportingFile extends BaseDocument
   # name: user's file name
   # status: current status or error message displayed to user
   # readProgress: progress of reading from file, in %
@@ -15,7 +15,7 @@
   #   importing: file currently being imported
   #   finished: file processing finished successfully, but it
   #             was not imported (import canceled or already exists)
-  #   imported: file processing finished successfully and at was imported
+  #   imported: file processing finished successfully and it was imported
   #   errored: file processing errored

   @Meta
@@ -317,10 +317,16 @@
 Template.importingFilesItem.hideCancel = ->
   # We keep cancel shown even when canceled is set, until we get back
   # in the file upload method callback and set finished as well
-  @state in ['finished', 'errored']
+  @state in ['finished', 'errored', 'imported']

 Template.importingFilesItem.state = ->
-  return 'canceled' if @canceled
+  # Canceled could still be set, but state could be errored
+  # or imported if canceled was set to late in the process,
+  # in which case we want not to display it as canceled
+  return @state if @state in ['errored', 'imported']
+  # But otherwise if state is finished and canceled,
+  # we want to display it as canceled
+  return 'canceled' if @canceled and @state is 'finished'
   return @state

 Template.importingFilesItem.publication = ->
@@ -444,6 +450,8 @@
     $('body').add('html').removeClass 'overlay-active'

 Deps.autorun ->
+  return
+
   importingFilesCount = ImportingFile.documents.find().count()

   return unless importingFilesCount


port patchStackTrace from Redux

port patchStackTrace from Redux

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -96,7 +96,7 @@
 # setting `__filename`, `__dirname`, and relative `require()`.
 exports.run = (code, options = {}) ->
   mainModule = require.main
-
+  options.sourceMap = true
   # Set the filename.
   mainModule.filename = process.argv[1] =
       if options.filename then fs.realpathSync(options.filename) else '.'
@@ -109,7 +109,12 @@

   # Compile.
   if not helpers.isCoffee(mainModule.filename) or require.extensions
-    mainModule._compile compile(code, options), mainModule.filename
+    answer = compile(code, options)
+    # Attach sourceMap object to mainModule._sourceMaps[options.filename] so that
+    # it is accessible by Error.prepareStackTrace.
+    do patchStackTrace
+    mainModule._sourceMaps[mainModule.filename] = answer.sourceMap
+    mainModule._compile answer.js, mainModule.filename
   else
     mainModule._compile code, mainModule.filename

@@ -181,4 +186,95 @@
   # data for this token. Unfortunately, Jison seems to send an outdated `loc`
   # (from the previous token), so we take the location information directly
   # from the lexer.
-  helpers.throwSyntaxError message, parser.lexer.yylloc+  helpers.throwSyntaxError message, parser.lexer.yylloc
+
+# Based on [michaelficarra/CoffeeScriptRedux](http://goo.gl/ZTx1p)
+# NodeJS / V8 have no support for transforming positions in stack traces using
+# sourceMap, so we must monkey-patch Error to display CoffeeScript source
+# positions.
+
+# Ideally, this would happen in a way that is scalable to multiple compile-to-
+# JS languages trying to do the same thing in the same NodeJS process. We can
+# implement it as if there were an API, and then patch in support for that
+# API. The following maybe should be in its own npm module that multiple
+# compilers can include.
+
+patched = false
+patchStackTrace = ->
+  return if patched
+  patched = true
+  mainModule = require.main
+  # Map of filenames -> functions that return a sourceMap string.
+  mainModule._sourceMaps = {}
+
+  # (Assigning to a property of the Module object in the normal module cache is
+  # unsuitable, because node deletes those objects from the cache if an
+  # exception is thrown in the module body.)
+
+  Error.prepareStackTrace = (err, stack) ->
+    sourceFiles = {}
+
+    getSourceMapping = (filename, line, column) ->
+      sourceMap = mainModule._sourceMaps[filename]
+      answer = sourceMap.getSourcePosition [line, column] if sourceMap
+      answer
+
+    frames = for frame in stack
+      break if frame.getFunction() is exports.run
+      "  at #{formatSourcePosition frame, getSourceMapping}"
+
+    "#{err.name}: #{err.message ? ''}\n#{frames.join '\n'}\n"
+
+# Based on http://v8.googlecode.com/svn/branches/bleeding_edge/src/messages.js
+# Modified to handle sourceMap
+formatSourcePosition = (frame, getSourceMapping) ->
+  fileName = undefined
+  fileLocation = ''
+
+  if frame.isNative()
+    fileLocation = "native"
+  else
+    if frame.isEval()
+      fileName = frame.getScriptNameOrSourceURL()
+      fileLocation = "#{frame.getEvalOrigin()}, " unless fileName
+    else
+      fileName = frame.getFileName()
+
+    fileName or= "<anonymous>"
+
+    line = frame.getLineNumber()
+    column = frame.getColumnNumber()
+
+    # Check for a sourceMap position
+    source = getSourceMapping fileName, line, column
+    fileLocation =
+      if source
+        "#{fileName}:#{source[0]}:#{source[1]}, <js>:#{line}:#{column}"
+      else
+        "#{fileName}:#{line}:#{column}"
+
+
+  functionName = frame.getFunctionName()
+  isConstructor = frame.isConstructor()
+  isMethodCall = not (frame.isToplevel() or isConstructor)
+
+  if isMethodCall
+    methodName = frame.getMethodName()
+    typeName = frame.getTypeName()
+
+    if functionName
+      tp = as = ''
+      if typeName and functionName.indexOf typeName
+        tp = "#{typeName}."
+      if methodName and functionName.indexOf(".#{methodName}") isnt functionName.length - methodName.length - 1
+        as = " [as #{methodName}]"
+
+      "#{tp}#{functionName}#{as} (#{fileLocation})"
+    else
+      "#{typeName}.#{methodName or '<anonymous>'} (#{fileLocation})"
+  else if isConstructor
+    "new #{functionName or '<anonymous>'} (#{fileLocation})"
+  else if functionName
+    "#{functionName} (#{fileLocation})"
+  else
+    fileLocation


removed noWrap (setTimeout evaluates on global)

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -75,7 +75,7 @@
 # This happens on page load.
 if document?.getElementsByTagName
   grind = (coffee) ->
-    setTimeout exports.compile coffee, noWrap: true
+    setTimeout exports.compile coffee
   grindRemote = (url) ->
     xhr = new (window.ActiveXObject or XMLHttpRequest)('Microsoft.XMLHTTP')
     xhr.open 'GET', url, true


focus to page editing area when page loaded

--- components/page.cjsx
+++ components/page.cjsx
@@ -80,7 +80,6 @@
   ))
   PageApp.titleRegion.attachView(new PageNameFormView())
   pageContentView = new PageContentView()
-  pageContentView.focus()
   PageApp.contentRegion.attachView(pageContentView)

   $("body").click ->
@@ -100,6 +99,8 @@
 PageContent = React.createClass(
   getInitialState: ->
     {content: page.content}
+  componentDidMount: ->
+    @refs.content.getDOMNode().focus()
   handlePageSubmit: ->
     ce = $("<pre />").html(React.findDOMNode(@refs.content).innerHTML)
     if($.browser.webkit)


login page route

login page route

--- imports/startup/routes.cjsx
+++ imports/startup/routes.cjsx
@@ -4,16 +4,18 @@
 App     = require '../ui/App.cjsx'
 Page    = require '../ui/Page.cjsx'
 AppsPage            = require '../ui/pages/meteor/AppsPage.coffee'
-AppsDetailPage  = require '../ui/pages/meteor/AppsDetailPage.coffee'
+AppsDetailPage      = require '../ui/pages/meteor/AppsDetailPage.coffee'
 InstancesPage       = require '../ui/pages/meteor/InstancesPage.coffee'
 InstanceDetailPage  = require '../ui/pages/meteor/InstanceDetailPage.coffee'
 StoragePage         = require '../ui/pages/meteor/StoragePage.coffee'
-
+LoginPage           = require '../ui/pages/meteor/LoginPage.coffee'

 module.exports = (props) ->

   <Router history={browserHistory} >
     <Route path="/" component={App} App={props}>
+      <IndexRoute component={LoginPage} App={props} />
+      <Route path="login" component={LoginPage} App={props} />
       <Route path="apps" component={AppsPage} App={props} />
       <Route path="apps/:name/:version" component={AppsDetailPage} App={props} />
       <Route path="instances" component={Page} title='Instances'>


chore(Gruntfile): Fixed grunt bump to allow for specifying version

--- misc/grunt/tasks.coffee
+++ misc/grunt/tasks.coffee
@@ -105,8 +105,17 @@
     if version? and not VERSION_REGEX.test version
       grunt.fail.fatal "Invalid tag"

+    writeAndChangelog = (newVersion) ->
+      grunt.log.writeln "Updating to version #{newVersion}"
+
+      pkg         = grunt.config.get("pkg")
+      pkg.version = newVersion
+      grunt.file.write "package.json", JSON.stringify(pkg, null, "  "), encoding: "utf8"
+
+      grunt.task.run "changelog"
+
     if version?
-      grunt.log.writeln version
+      writeAndChangelog version[1..]
     else
       getLastVersion (error, data) ->
         grunt.fail.fatal "Failed to read last tag" if error?
@@ -117,13 +126,7 @@
         versionArr[2] = +versionArr[2] + 1
         data          = versionArr.join "."

-        grunt.log.writeln "Updating to version #{data}"
-
-        pkg         = grunt.config.get("pkg")
-        pkg.version = data[1..]
-        grunt.file.write "package.json", JSON.stringify(pkg, null, "  "), encoding: "utf8"
-
-        grunt.task.run "changelog"
+        writeAndChangelog data[1..]

         done()


Fix for Firefox

Fix for Firefox

--- app/assets/javascripts/activeadmin-wysihtml5/base.js.coffee
+++ app/assets/javascripts/activeadmin-wysihtml5/base.js.coffee
@@ -156,7 +156,7 @@

   $ ->
     $('.activeadmin-wysihtml5').activeAdminWysihtml5()
-    $(document).on "click", "a", ->
+    $("a").bind "click", ->
       setTimeout(
         -> $('.activeadmin-wysihtml5').activeAdminWysihtml5()
         50


Fix sorting.

Fix sorting.

--- app/assets/javascripts/admin_reserve_view_manager.js.coffee
+++ app/assets/javascripts/admin_reserve_view_manager.js.coffee
@@ -43,8 +43,10 @@
       diff_a = parseInt(a["duration_number"])#moment(a["end"]).tz("America/Los_Angeles").diff(moment(a["start"]).tz("America/Los_Angeles"), 'minutes')
       diff_b = parseInt(b["duration_number"])#moment(b["end"]).tz("America/Los_Angeles").diff(moment(b["start"]).tz("America/Los_Angeles"), 'minutes')
       return 1 if diff_b > diff_a
-      return -1 if diff_b < diff_a
-      return a["room-name"].localeCompare(b["room-name"])
+      return -1 if diff_b <
+      return 1 if a["room-name"] > b["room-name"]
+      return -1 if a["room-name"] < b["room-name"]
+      return 0
     max_duration = parseInt(objects[0]["duration_number"])
     for object in objects
       object["width"] = 500*parseInt(object["duration_number"])/max_duration


Version 1.0.5

Version 1.0.5

* Accepts success and error parameters on commit and model.save
* Do not trigger individual model attributes 'change' event upon server
refresh

--- backbone.rails_store.js.coffee
+++ backbone.rails_store.js.coffee
@@ -1,7 +1,7 @@
 ###
 *  Copyright (C) 2013 - Raphael Derosso Pereira <raphaelpereira@gmail.com>
 *
-*  Backbone.RailsStore - version 1.0.4
+*  Backbone.RailsStore - version 1.0.5
 *
 *  Backbone extensions to provide complete Rails interaction on CoffeeScript/Javascript,
 *  keeping single reference models in memory, reporting refresh conflicts and consistently
@@ -278,12 +278,17 @@

     Persists all changes to server and performs a sync after that

-    Events:
+    Parameters:
+      success - Called upon success
+      error - Called upon error
+
+    Triggers:
       commit:start - just after starting server request
       commit:done - after successful server request response
       commit:failed - if something wrong happened
   ###
-  commit: ->
+  commit: (options) ->
+    options = options || {}
     commitData = {}
     _.each @_changedModels, (models, modelType) =>
       _.each models, (model) =>
@@ -347,8 +352,10 @@
           @_storeServer.unset('errors')
           _.each triggerModels, (changedModel) =>
             changedModel.trigger 'commit:done', changedModel
-          @trigger('commit:done', @) if not errors
+          @trigger('commit:done', @) unless errors
           @trigger('commit:failed', {model: model, errors: errors}) if errors
+          options.success() unless errors
+          options.error() if errors
           # TODO: handle rollback
         catch e
           console.log(e)
@@ -654,8 +661,8 @@
     storedModel = @findById(modelType, model.id)
     unless storedModel
       serverModel = new @_types[modelType]({id: model.id}, {silent:true})
-      serverModel.set(model, {parse:true, storeSync:true})
-      #serverModel.syncAttributes = _.clone(serverModel.attributes)
+      serverModel.set(model, {parse:true, storeSync:true, silent:true})
+      serverModel.trigger('change', serverModel)
       @reportSync(serverModel)
       serverModel.trigger('refresh:done', serverModel)
       return serverModel
@@ -675,7 +682,8 @@
           hasConflict = true
           storedModel.trigger("refresh:conflict:#{key}", storedModel)
         )
-        storedModel.set(noConflictAttr,{storeSync:true})
+        storedModel.set(noConflictAttr,{storeSync:true, silent:true})
+        storedModel.trigger('change', storedModel)
         @reportSync(storedModel)
         storedModel.syncAttributes = _.extend(syncAttr, noConflictAttr)
         if hasConflict
@@ -684,7 +692,8 @@
         else
           delete storedModel.conflict
       else if serverChangedAttr
-        storedModel.set(serverChangedAttr, {storeSync:true})
+        storedModel.set(serverChangedAttr, {storeSync:true, silent:true})
+        storedModel.trigger('change', storedModel)
       @reportSync(storedModel)

       storedModel.trigger('refresh:done', storedModel)
@@ -1172,8 +1181,8 @@
   ###
   backboneSave: @prototype.save

-  save: ->
-    @_store.commit()
+  save: (options) ->
+    @_store.commit(options)

   ###
     destroy - removes from server


#393 Fix url manager in view mode

#393 Fix url manager in view mode

--- app/assets/javascripts/oxalis/controller/url_manager.coffee
+++ app/assets/javascripts/oxalis/controller/url_manager.coffee
@@ -52,7 +52,9 @@
     @listenTo(@model.flycam, "changed", @update)
     @listenTo(@model.flycam3d, "changed", @update)
     @listenTo(app.vent, "changeViewMode", @update)
-    @listenTo(@model.skeletonTracing, "newActiveNode", @update)
+
+    if @model.skeletonTracing?
+      @listenTo(@model.skeletonTracing, "newActiveNode", @update)

   buildUrl : ->


QuickStartBar: fix discussion creation

QuickStartBar: fix discussion creation

Change-Id: Icbbb063e1c0e4114475cc6f263e477f66aca22dc

--- app/coffeescripts/views/QuickStartBar/DiscussionView.coffee
+++ app/coffeescripts/views/QuickStartBar/DiscussionView.coffee
@@ -9,8 +9,9 @@

   class DiscussionView extends BaseItemView

-    events:
+    events: _.extend
       'change [name=graded]': 'onGradedClick'
+    , BaseItemView::events

     template: template


Fix rounding of very small Euro values

Fix rounding of very small Euro values

Closes #1994

--- app/assets/javascripts/lib/models/metric.coffee
+++ app/assets/javascripts/lib/models/metric.coffee
@@ -151,7 +151,13 @@
     prefix = if x < 0 then "-" else ""
     abs_value = Math.abs(x)
     scale     = @power_of_thousand(x)
-    value     = abs_value / Math.pow(1000, scale)
+
+    if scale < 0
+      precision = scale = 0
+      value = abs_value
+    else
+      value = abs_value / Math.pow(1000, scale)
+
     suffix    = ''
     rounded   = @round_number(value, precision).toString()


added currentStudentName to #header

added currentStudentName to #header

--- app/assets/javascripts/views/map_view.js.coffee
+++ app/assets/javascripts/views/map_view.js.coffee
@@ -3,9 +3,9 @@

Change student:

-    <select name="student_name">
-      <option value="Student Name">Student Name</option>
-    </select>
+

+      <%= currentStudentName %>
+

Last updated:

@@ -21,6 +21,7 @@
   render: ->
     markup = template.studentList
       lastUpdatedAt: @currentAssignment.get('last_updated_at')
+      currentStudentName: @currentAssignment.get('student_name')

     @$el.html markup


consistent braces

consistent braces

--- client/apps/edit/client/sections.coffee
+++ client/apps/edit/client/sections.coffee
@@ -46,10 +46,10 @@
     div { className: 'edit-section-tool', 'data-state-open': @state.open },
       div { className: 'edit-section-tool-icon', onClick: @toggle }
       ul { className: 'edit-section-tool-menu' },
-        li
+        li {
           className: 'edit-section-tool-text'
           onClick: @newTextSection
-        , 'Text'
+        }, 'Text'
         li {
           className: 'edit-section-tool-artworks'
         }, 'Artworks'


PwmWrite: don't use null for chipNumber

PwmWrite: don't use null for chipNumber

--- components/PwmWrite.coffee
+++ components/PwmWrite.coffee
@@ -10,7 +10,7 @@
       dutycycle: new noflo.Port 'number'
       period: new noflo.Port 'number'

-    @chipNumber = null
+    @chipNumber = 0
     @dutyCycle = null
     @period = null


IDEView: Fixed context menu offscreen issue. [fixes #80071072]

--- client/ide/views/tabview/ideview.coffee
+++ client/ide/views/tabview/ideview.coffee
@@ -269,15 +269,16 @@
         delete @menu

   createPlusContextMenu: ->
-    offset        = @holderView.plusHandle.$().offset()
-    contextMenu   = new KDContextMenu
-      delegate    : this
-      x           : offset.left - 133
-      y           : offset.top  + 30
-      arrow       :
-        placement : 'top'
-        margin    : -20
-    , @getPlusMenuItems()
-
-    contextMenu.once 'ContextMenuItemReceivedClick', ->
-      contextMenu.destroy()
+    offset      = @holderView.plusHandle.$().offset()
+    offsetLeft  = offset.left - 133
+    margin      = if offsetLeft >= -1 then -20 else 12
+    placement   = 'top'
+    options     =
+      delegate  : this
+      x         : Math.max 0, offsetLeft
+      y         : offset.top + 30
+      arrow     : { placement, margin }
+
+    contextMenu = new KDContextMenu options, @getPlusMenuItems()
+
+    contextMenu.once 'ContextMenuItemReceivedClick', -> contextMenu.destroy()


IDEView: Add event listener for update of the collaboration data.

--- client/ide/views/tabview/ideview.coffee
+++ client/ide/views/tabview/ideview.coffee
@@ -22,6 +22,7 @@
     @tabView.on 'ViewNeedsToBeShown',       @bound 'showView'
     @tabView.on 'TabNeedsToBeClosed',       @bound 'closeTabByFile'
     @tabView.on 'GoToLineRequested',        @bound 'goToLine'
+    @tabView.on 'CollaborationDataUpdated', @bound 'checkCollaborationData'

     @tabView.on 'FileNeedsToBeOpened', (file, contents, callback) =>
       @closeUntitledFileIfNotChanged()


Profile: Re-enabled openContent method in router

--- client/landing/Core/kodingrouter.coffee
+++ client/landing/Core/kodingrouter.coffee
@@ -82,39 +82,39 @@

   setPageTitle: (title = 'Koding') -> document.title = Encoder.htmlDecode title

-  # openContent : (name, section, models, route, query, passOptions=no) ->
-  #   method   = 'createContentDisplay'
-  #   [models] = models  if Array.isArray models
+  openContent : (name, section, models, route, query, passOptions=no) ->
+    method   = 'createContentDisplay'
+    [models] = models  if Array.isArray models

-  #   # HK: with passOptions false an application only gets the information
-  #   # 'hey open content' with this model. But some applications require
-  #   # more information such as the route. Unfortunately we would need to
-  #   # refactor a lot legacy. For now we do this new thing opt-in
-  #   if passOptions
-  #     method += 'WithOptions'
-  #     options = {model:models, route, query}
+    # HK: with passOptions false an application only gets the information
+    # 'hey open content' with this model. But some applications require
+    # more information such as the route. Unfortunately we would need to
+    # refactor a lot legacy. For now we do this new thing opt-in
+    if passOptions
+      method += 'WithOptions'
+      options = {model:models, route, query}

-  #   callback = =>
-  #     KD.getSingleton("appManager").tell section, method, options ? models, (contentDisplay) =>
-  #       unless contentDisplay
-  #         console.warn 'no content display'
-  #         return
-  #       routeWithoutParams = route.split('?')[0]
-  #       @openRoutes[routeWithoutParams] = contentDisplay
-  #       @openRoutesById[contentDisplay.id] = routeWithoutParams
-  #       contentDisplay.emit 'handleQuery', query
+    callback = =>
+      KD.getSingleton("appManager").tell section, method, options ? models, (contentDisplay) =>
+        unless contentDisplay
+          console.warn 'no content display'
+          return
+        routeWithoutParams = route.split('?')[0]
+        @openRoutes[routeWithoutParams] = contentDisplay
+        @openRoutesById[contentDisplay.id] = routeWithoutParams
+        contentDisplay.emit 'handleQuery', query

-  #   groupsController = KD.getSingleton('groupsController')
-  #   currentGroup = groupsController.getCurrentGroup()
+    groupsController = KD.getSingleton('groupsController')
+    currentGroup = groupsController.getCurrentGroup()

-  #   # change group if necessary
-  #   unless currentGroup
-  #     groupName = if section is "Groups" then name else "koding"
-  #     groupsController.changeGroup groupName, (err) =>
-  #       KD.showError err if err
-  #       callback()
-  #   else
-  #     callback()
+    # change group if necessary
+    unless currentGroup
+      groupName = if section is "Groups" then name else "koding"
+      groupsController.changeGroup groupName, (err) =>
+        KD.showError err if err
+        callback()
+    else
+      callback()

   loadContent: (name, section, slug, route, query, passOptions) ->


added error reducer to exported object

added error reducer to exported object

--- imports/redux/reducers/index.coffee
+++ imports/redux/reducers/index.coffee
@@ -1,3 +1,4 @@
 module.exports =
   collections: require './collections.coffee'
   search: require './search.coffee'
+  error: require './error.coffee'


require SSL on server in production

require SSL on server in production

--- config/server.coffee
+++ config/server.coffee
@@ -10,8 +10,14 @@
 #

 module.exports = drawRoutes: (app) ->
-  app.post "/login", (req, res) ->
-    res.json message: "logging in!"
+  app.use(requireHTTPS) if process.env.NODE_ENV == 'production'

-  app.post "/logout", (req, res) ->
-    res.json message: "logging out!"
+requireHTTPS = (req, res, next) ->
+  res.setHeader('Strict-Transport-Security', 'max-age=31536000')
+  isSecure = req.secure || req.headers['x-forwarded-proto'] == 'https'
+
+  if isSecure
+    next()
+  else
+    res.redirect "https://#{req.get('host')}#{req.url}"
+


ComputeController: on reset if render requested update EnvDataProvider as well

--- client/app/lib/providers/computecontroller.coffee
+++ client/app/lib/providers/computecontroller.coffee
@@ -83,11 +83,15 @@
     @plans        = null
     @_trials      = {}

-    if render then @fetchStacks =>
-      @info machine for machine in @machines
-      @emit "RenderMachines", @machines
-      @emit "RenderStacks",   @stacks
-      callback null
+    if render
+      environmentDataProvider = require 'app/userenvironmentdataprovider'
+      environmentDataProvider.fetch =>
+        @fetchStacks =>
+          @info machine for machine in @machines
+          @emit "RenderMachines", @machines
+          @emit "RenderStacks",   @stacks
+          callback null
+      , yes

     return this

@@ -434,8 +438,7 @@
           remote.api.JWorkspace.deleteByUid machine.uid, (err)->
             console.warn "couldn't delete workspace:", err  if err

-          environmentDataProvider = require 'app/userenvironmentdataprovider'
-          environmentDataProvider.fetch => @reset yes, ->
+          @reset yes, ->
             kd.singletons.appManager.tell 'IDE', 'quit'

         return


ComputeController: shareCredential: we need to set accessLevel as write

This one is required to list credential on other admins list which was
described before in the original place that I introduce this flow;

  # accessLevel defined here is only valid for admins in this group
  # which means only admins in the group and owner of this credential
  # can make changes on the data of it ~ GG

https://github.com/koding/koding/blob/master/client/app/lib/util/stacks/updatecustomvariable.coffee#L10-L15

--- client/app/lib/providers/computecontroller.coffee
+++ client/app/lib/providers/computecontroller.coffee
@@ -1252,7 +1252,7 @@
     if creds.length > 0 and credential = credentials["#{selectedProvider}"]?.first
       remote.api.JCredential.one credential, (err, credential) ->
         { slug } = groupsController.getCurrentGroup()
-        credential.shareWith { target: slug }, (err) ->
+        credential.shareWith { target: slug, accessLevel: 'write' }, (err) ->
           showError 'Failed to share credential'  if err
           callback()
     else showError 'Failed to share credential'


User Environment Data Provider: Rename parameter

--- client/app/lib/userenvironmentdataprovider.coffee
+++ client/app/lib/userenvironmentdataprovider.coffee
@@ -77,12 +77,12 @@
     return own.concat shared.concat collaboration

-  fetchMachine: (labelOrUId, callback) ->
+  fetchMachine: (identifier, callback) ->

-    @fetchMachineByLabel labelOrUId, (machine) =>
+    @fetchMachineByLabel identifier, (machine) =>
       return  callback new Machine { machine }  if machine

-      @fetchMachineByUId labelOrUId, (machine) =>
+      @fetchMachineByUId identifier, (machine) =>
         machine = if machine then new Machine { machine } else null

         callback machine


general fix for the Infinity bug raised in #416

--- c3_renderers.coffee
+++ c3_renderers.coffee
@@ -68,18 +68,14 @@
                 rowHeader = rowKey.join("-")
                 row = [if rowHeader == "" then pivotData.aggregatorName else rowHeader]
                 for colKey in colKeys
-                    agg = pivotData.getAggregator(rowKey, colKey)
-                    if agg.value()?
-                        val = agg.value()
-                        if $.isNumeric val
-                            if val < 1
-                                row.push parseFloat(val.toPrecision(3))
-                            else
-                                row.push parseFloat(val.toFixed(3))
+                    val = parseFloat  pivotData.getAggregator(rowKey, colKey).value()
+                    if isFinite(val)
+                        if val < 1
+                            row.push val.toPrecision(3)
                         else
-                            row.push val
-
-                    else row.push null
+                            row.push val.toFixed(3)
+                    else
+                        row.push null
                 columns.push row

             vAxisTitle = pivotData.aggregatorName+


Fix test

Fix test

--- cms/static/coffee/spec/models/section_spec.coffee
+++ cms/static/coffee/spec/models/section_spec.coffee
@@ -44,10 +44,10 @@
             @requests[0].respond(200)
             expect(CMS.Models.Section.prototype.hideNotification).toHaveBeenCalled()

-        it "show/hide a notification when saving fails", ->
+        it "don't hide notification when saving fails", ->
+            # this is handled by the global AJAX error handler
             @model.save()
-            expect(CMS.Models.Section.prototype.showNotification).toHaveBeenCalled()
             @requests[0].respond(500)
-            expect(CMS.Models.Section.prototype.hideNotification).toHaveBeenCalled()
+            expect(CMS.Models.Section.prototype.hideNotification).not.toHaveBeenCalled()


test error case for inviteToProject

test error case for inviteToProject

--- services/web/test/UnitTests/coffee/Collaborators/CollaboratorsInviteHandlerTests.coffee
+++ services/web/test/UnitTests/coffee/Collaborators/CollaboratorsInviteHandlerTests.coffee
@@ -79,3 +79,13 @@
 					@CollaboratorsEmailHandler.notifyUserOfProjectInvite.callCount.should.equal 1
 					@CollaboratorsEmailHandler.notifyUserOfProjectInvite.calledWith(@projectId, @email).should.equal true
 					done()
+
+		describe 'when saving model produces an error', ->
+
+			beforeEach ->
+				@ProjectInvite::save = sinon.spy (cb) -> cb(new Error('woops'), this)
+
+			it 'should produce an error', (done) ->
+				@call (err, invite) =>
+					expect(err).to.be.instanceof Error
+					done()


models: jinvitation schema updated, role added @cihangir

--- workers/social/lib/social/models/invitation.coffee
+++ workers/social/lib/social/models/invitation.coffee
@@ -90,6 +90,9 @@
       modifiedAt    :
         type        : Date
         default     : -> new Date
+      role          :
+        type        : String
+        default     : -> 'member'

   accept$: secure (client, callback) ->
     { delegate } = client.connection
@@ -164,7 +167,7 @@

         queue = invitations.map (invitation) -> ->

-          { email, firstName, lastName } = invitation
+          { email, firstName, lastName, role } = invitation

           JInvitation.one { email, groupName }, (err, invitation) ->
             return callback new KodingError err  if err
@@ -190,6 +193,7 @@
                 hash
                 email
                 groupName
+                role
               }
               # firstName and lastName are optional
               data.firstName = firstName  if firstName


hotfix grunt config for grunt-release

hotfix grunt config for grunt-release

--- config/grunt/ghrelease.coffee
+++ config/grunt/ghrelease.coffee
@@ -3,6 +3,7 @@
     options:
       additionalFiles: ['bower.json']
       npm: false
+      tagName: 'v<%= version %>'
       github:
         repo: 'casetext/quill'
         usernameVar: 'GITHUB_TOKEN',


add a callback

add a callback

--- config/initializers/db.coffee
+++ config/initializers/db.coffee
@@ -90,6 +90,7 @@
                                         if err
                                             compound.logger.write "Error on database" +
                                             " Add admin : #{err}"
+                                            callback()
                                         else
                                             logFound()
                                             callback()


Activity: EmbedBox: Resize window when embed image loaded.

--- client/activity/lib/views/embedbox/embedboximageview.coffee
+++ client/activity/lib/views/embedbox/embedboximageview.coffee
@@ -25,7 +25,11 @@

     @addSubView @image  = new KDCustomHTMLView
       tagName     : 'img'
+      bind        : 'load'
       attributes  :
         src       : proxifyUrl oembed.images?[0]?.url, {width}
         title     : oembed.title or ''
         width     : "100%"
+
+    @image.on 'load', (event) =>
+      kd.singletons.windowController.notifyWindowResizeListeners()


Fixed displaying raster image thangs in the thang editor.

--- app/views/editor/thang/ThangTypeEditView.coffee
+++ app/views/editor/thang/ThangTypeEditView.coffee
@@ -241,10 +241,8 @@
     @updatePortrait()

   showRasterImage: ->
-    sprite = new Lank(@thangType, @getLankOptions())
-    @currentLank?.destroy()
-    @currentLank = sprite
-    @showSprite(sprite.sprite)
+    lank = new Lank(@thangType, @getLankOptions())
+    @showLank(lank)
     @updateScale()

   onNewSpriteSheet: ->


payment: only set current subscription if it exists

--- client/landing/app/MainApp/payment/paymentcontroller.coffee
+++ client/landing/app/MainApp/payment/paymentcontroller.coffee
@@ -106,7 +106,7 @@

       dash queue, ->
         form.setPlans plans
-        form.setCurrentSubscription subscription, forceUpgrade
+        form.setCurrentSubscription subscription, forceUpgrade  if subscription

     return form


fixes previous commit about transitionend event of activitylistitems

--- client/landing/app/Applications/Activity.kdapplication/ListItems/ActivityListItem.coffee
+++ client/landing/app/Applications/Activity.kdapplication/ListItems/ActivityListItem.coffee
@@ -72,9 +72,9 @@
       @addChildView teaser, => @slideIn()

   slideIn:(callback=noop)->
-    @once 'transition', callback.bind @
+    @once 'transitionend', callback.bind @
     @unsetClass 'hidden-item'

   slideOut:(callback=noop)->
-    @once 'transition', callback.bind @
+    @once 'transitionend', callback.bind @
     @setClass 'hidden-item'


Added tracking to statemachine

Added tracking to statemachine

--- core/app/assets/javascripts/tour.coffee
+++ core/app/assets/javascripts/tour.coffee
@@ -76,6 +76,8 @@
             "",
             { side: 'left', align: 'top', contentView: view }

+            mp_track "Tour: Loaded tour"
+
         onleavestarted: =>
           @tooltipRemove '.create-your-first-factlink-content > p:first'
           @state.transition()
@@ -86,6 +88,8 @@
             "That was easy!",
             "",
             { side: 'left', align: 'top', alignMargin: 60, contentView: view }
+
+          mp_track "Tour: Selected text"

         onleavetext_selected: =>
           @tooltipRemove '#extension-button',


add hashchange listener

add hashchange listener

--- site.coffee
+++ site.coffee
@@ -66,6 +66,11 @@
     indexContent.style.left = "0%"
   indexOpened = !indexOpened

+hashChange = ->
+  hash = window.location.hash.replace(/^#|\.webm$/, '').trim()
+  if hash.length > 0
+    playWebMByFileName(hash + ".webm")
+
 load = ->
   videoplayer = document.getElementById('videoplayer')
   content = document.getElementsByClassName('wrapper')[0]
@@ -91,4 +96,5 @@
     else
       playRandomWebM()

+window.addEventListener "hashchange", hashChange, false
 document.addEventListener "DOMContentLoaded", load, false


Added test for n-else

Added test for n-else

--- tests/integration/src/document/condition.server.coffee
+++ tests/integration/src/document/condition.server.coffee
@@ -32,3 +32,22 @@
         assert.is view.node.stringify(), 'FAIL'
         elem.props.set 'x', 2
         assert.is view.node.stringify(), 'OK'
+
+    it 'synchronizes changes with n-else', ->
+        source = createView '''
+            <n-component name="Test">
+                 1}">YES
+                NO
+            </n-component>
+            <Test x="1" />
+        '''
+        view = source.clone()
+        elem = view.node.children[0]
+
+        renderParse view
+        assert.is view.node.stringify(), 'NO'
+        elem.props.set 'x', 2
+        assert.is view.node.stringify(), 'YES'
+
+        elem.props.set 'x', 1
+        assert.is view.node.stringify(), 'NO'


Open only one Redis client

Open only one Redis client

--- nodejs/vie-redis.coffee
+++ nodejs/vie-redis.coffee
@@ -7,9 +7,9 @@
         return false
     return true

+redisClient = redis.createClient()
+
 Backbone.sync = (method, model, success, error) ->
-    redisClient = redis.createClient()
-
     toUUID = () ->
         S4 = () -> ((1 + Math.random()) * 0x10000|0).toString(16).substring 1
         "#{S4()}#{S4()}-#{S4()}-#{S4()}-#{S4()}-#{S4()}#{S4()}#{S4()}"


remove comment

remove comment

--- lib/python-debugger-view.coffee
+++ lib/python-debugger-view.coffee
@@ -22,7 +22,6 @@
   getDebuggerPath: ->
     pkgs = atom.packages.getPackageDirPaths()[0]
     debuggerPath = path.join(pkgs, "python-debugger", "resources")
-    # console.log("Debugger path: ", debuggerPath)
     return debuggerPath

   @content: ->


:lipstick: Add subscribe helper

:lipstick: Add subscribe helper

--- lib/selection-count-view.coffee
+++ lib/selection-count-view.coffee
@@ -1,18 +1,21 @@
-module.exports =
 class SelectionCountView extends HTMLElement
   initialize: (@statusBar) ->
     @classList.add('selection-count', 'inline-block')

     @activeItemSubscription = atom.workspace.onDidChangeActivePaneItem (activeItem) =>
-      @selectionSubscription?.dispose()
-      @selectionSubscription = activeItem?.onDidChangeSelectionRange? => @updateCount()
+      @subscribeToActiveTextEditor()

-    @selectionSubscription = atom.workspace.getActiveTextEditor()?.onDidChangeSelectionRange => @updateCount()
-    @updateCount()
+    @subscribeToActiveTextEditor()

   destroy: ->
     @activeItemSubscription.dispose()
     @selectionSubscription?.dispose()
+
+  subscribeToActiveTextEditor: ->
+    @selectionSubscription?.dispose()
+    @selectionSubscription = atom.workspace.getActiveTextEditor()?.onDidChangeSelectionRange =>
+      @updateCount()
+    @updateCount()

   updateCount: ->
     count = atom.workspace.getActiveTextEditor()?.getSelectedText().length


remove commented-out code

remove commented-out code

--- src/collection/index.coffee
+++ src/collection/index.coffee
@@ -70,10 +70,6 @@
       @_sourceBinding = source.bind().to(@).now()
       return @

-    #oldSrc = @_source or []
-    #src = @_source = @_transform source
-    #@_length = src.length
-    #@emit "reset", src, oldSrc
     @_insert @_source = @_transform source
     @_resetInfo()


Only One Index File

Only One Index File

--- lib/transforms/indexFile.coffee
+++ lib/transforms/indexFile.coffee
@@ -5,10 +5,14 @@
 path = require 'path'
 map = require 'map-stream'

-module.exports = (indexFile) ->
+module.exports = (indexFile='') ->
+  # Process only one index file
+  found = false
+
   modifyFile = (file, cb) ->
-    if file.originalRelative is indexFile
+    if not found and (file.originalRelative is indexFile)
       file.path = path.join(file.cwd, 'index.html')
+      found = true

     cb(null, file)


course registration to exercise working

course registration to exercise working

--- src/concept-coach/base.cjsx
+++ src/concept-coach/base.cjsx
@@ -67,7 +67,7 @@
     else if not isLoggedIn
       <UserLogin onComplete={@onLoginComplete} />
     else if not isRegistered
-      <CourseRegistration {...@props} onComplete={@update} />
+      <CourseRegistration {...@props} onComplete={@updateUser} />
     else
       course = User.getCourse(@props.collectionUUID)

@@ -76,7 +76,7 @@
       else if view is 'dashboard'
         coach = <Dashboard id={course.id}/>
       else if view is 'profile'
-        coach = <CourseRegistration {...@props} onComplete={@update} />
+        coach = <CourseRegistration {...@props} onComplete={@updateUser} />

   render: ->
     {isLoaded, isLoggedIn} = @state


add description

add description

--- snippets/line-class.cson
+++ snippets/line-class.cson
@@ -1,302 +1,402 @@
 # Generated with Atomizr – https://atom.io/packages/atomizr
 ".text.html":
   "line-icon-adjustments-class":
+    body: "<${1:span} class=\"icon-adjustments$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-adjustments-class"
-    body: "<${1:span} class=\"icon-adjustments$2\"></${1:span}>$2"
   "line-icon-alarmclock-class":
+    body: "<${1:span} class=\"icon-alarmclock$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-alarmclock-class"
-    body: "<${1:span} class=\"icon-alarmclock$2\"></${1:span}>$2"
   "line-icon-anchor-class":
+    body: "<${1:span} class=\"icon-anchor$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-anchor-class"
-    body: "<${1:span} class=\"icon-anchor$2\"></${1:span}>$2"
   "line-icon-aperture-class":
+    body: "<${1:span} class=\"icon-aperture$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-aperture-class"
-    body: "<${1:span} class=\"icon-aperture$2\"></${1:span}>$2"
   "line-icon-attachment-class":
+    body: "<${1:span} class=\"icon-attachment$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-attachment-class"
-    body: "<${1:span} class=\"icon-attachment$2\"></${1:span}>$2"
   "line-icon-bargraph-class":
+    body: "<${1:span} class=\"icon-bargraph$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-bargraph-class"
-    body: "<${1:span} class=\"icon-bargraph$2\"></${1:span}>$2"
   "line-icon-basket-class":
+    body: "<${1:span} class=\"icon-basket$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-basket-class"
-    body: "<${1:span} class=\"icon-basket$2\"></${1:span}>$2"
   "line-icon-beaker-class":
+    body: "<${1:span} class=\"icon-beaker$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-beaker-class"
-    body: "<${1:span} class=\"icon-beaker$2\"></${1:span}>$2"
   "line-icon-bike-class":
+    body: "<${1:span} class=\"icon-bike$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-bike-class"
-    body: "<${1:span} class=\"icon-bike$2\"></${1:span}>$2"
   "line-icon-book-open-class":
+    body: "<${1:span} class=\"icon-book-open$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-book-open-class"
-    body: "<${1:span} class=\"icon-book-open$2\"></${1:span}>$2"
   "line-icon-briefcase-class":
+    body: "<${1:span} class=\"icon-briefcase$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-briefcase-class"
-    body: "<${1:span} class=\"icon-briefcase$2\"></${1:span}>$2"
   "line-icon-browser-class":
+    body: "<${1:span} class=\"icon-browser$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-browser-class"
-    body: "<${1:span} class=\"icon-browser$2\"></${1:span}>$2"
   "line-icon-calendar-class":
+    body: "<${1:span} class=\"icon-calendar$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-calendar-class"
-    body: "<${1:span} class=\"icon-calendar$2\"></${1:span}>$2"
   "line-icon-camera-class":
+    body: "<${1:span} class=\"icon-camera$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-camera-class"
-    body: "<${1:span} class=\"icon-camera$2\"></${1:span}>$2"
   "line-icon-caution-class":
+    body: "<${1:span} class=\"icon-caution$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-caution-class"
-    body: "<${1:span} class=\"icon-caution$2\"></${1:span}>$2"
   "line-icon-chat-class":
+    body: "<${1:span} class=\"icon-chat$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-chat-class"
-    body: "<${1:span} class=\"icon-chat$2\"></${1:span}>$2"
   "line-icon-circle-compass-class":
+    body: "<${1:span} class=\"icon-circle-compass$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-circle-compass-class"
-    body: "<${1:span} class=\"icon-circle-compass$2\"></${1:span}>$2"
   "line-icon-clipboard-class":
+    body: "<${1:span} class=\"icon-clipboard$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-clipboard-class"
-    body: "<${1:span} class=\"icon-clipboard$2\"></${1:span}>$2"
   "line-icon-clock-class":
+    body: "<${1:span} class=\"icon-clock$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-clock-class"
-    body: "<${1:span} class=\"icon-clock$2\"></${1:span}>$2"
   "line-icon-cloud-class":
+    body: "<${1:span} class=\"icon-cloud$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-cloud-class"
-    body: "<${1:span} class=\"icon-cloud$2\"></${1:span}>$2"
   "line-icon-compass-class":
+    body: "<${1:span} class=\"icon-compass$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-compass-class"
-    body: "<${1:span} class=\"icon-compass$2\"></${1:span}>$2"
   "line-icon-desktop-class":
+    body: "<${1:span} class=\"icon-desktop$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-desktop-class"
-    body: "<${1:span} class=\"icon-desktop$2\"></${1:span}>$2"
   "line-icon-dial-class":
+    body: "<${1:span} class=\"icon-dial$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-dial-class"
-    body: "<${1:span} class=\"icon-dial$2\"></${1:span}>$2"
   "line-icon-document-class":
+    body: "<${1:span} class=\"icon-document$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-document-class"
-    body: "<${1:span} class=\"icon-document$2\"></${1:span}>$2"
   "line-icon-documents-class":
+    body: "<${1:span} class=\"icon-documents$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-documents-class"
-    body: "<${1:span} class=\"icon-documents$2\"></${1:span}>$2"
   "line-icon-download-class":
+    body: "<${1:span} class=\"icon-download$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-download-class"
-    body: "<${1:span} class=\"icon-download$2\"></${1:span}>$2"
   "line-icon-dribbble-class":
+    body: "<${1:span} class=\"icon-dribbble$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-dribbble-class"
-    body: "<${1:span} class=\"icon-dribbble$2\"></${1:span}>$2"
   "line-icon-edit-class":
+    body: "<${1:span} class=\"icon-edit$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-edit-class"
-    body: "<${1:span} class=\"icon-edit$2\"></${1:span}>$2"
   "line-icon-envelope-class":
+    body: "<${1:span} class=\"icon-envelope$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-envelope-class"
-    body: "<${1:span} class=\"icon-envelope$2\"></${1:span}>$2"
   "line-icon-expand-class":
+    body: "<${1:span} class=\"icon-expand$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-expand-class"
-    body: "<${1:span} class=\"icon-expand$2\"></${1:span}>$2"
   "line-icon-facebook-class":
+    body: "<${1:span} class=\"icon-facebook$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-facebook-class"
-    body: "<${1:span} class=\"icon-facebook$2\"></${1:span}>$2"
   "line-icon-flag-class":
+    body: "<${1:span} class=\"icon-flag$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-flag-class"
-    body: "<${1:span} class=\"icon-flag$2\"></${1:span}>$2"
   "line-icon-focus-class":
+    body: "<${1:span} class=\"icon-focus$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-focus-class"
-    body: "<${1:span} class=\"icon-focus$2\"></${1:span}>$2"
   "line-icon-gears-class":
+    body: "<${1:span} class=\"icon-gears$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-gears-class"
-    body: "<${1:span} class=\"icon-gears$2\"></${1:span}>$2"
   "line-icon-genius-class":
+    body: "<${1:span} class=\"icon-genius$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-genius-class"
-    body: "<${1:span} class=\"icon-genius$2\"></${1:span}>$2"
   "line-icon-gift-class":
+    body: "<${1:span} class=\"icon-gift$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-gift-class"
-    body: "<${1:span} class=\"icon-gift$2\"></${1:span}>$2"
   "line-icon-global-class":
+    body: "<${1:span} class=\"icon-global$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-global-class"
-    body: "<${1:span} class=\"icon-global$2\"></${1:span}>$2"
   "line-icon-globe-class":
+    body: "<${1:span} class=\"icon-globe$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-globe-class"
-    body: "<${1:span} class=\"icon-globe$2\"></${1:span}>$2"
   "line-icon-googleplus-class":
+    body: "<${1:span} class=\"icon-googleplus$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-googleplus-class"
-    body: "<${1:span} class=\"icon-googleplus$2\"></${1:span}>$2"
   "line-icon-grid-class":
+    body: "<${1:span} class=\"icon-grid$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-grid-class"
-    body: "<${1:span} class=\"icon-grid$2\"></${1:span}>$2"
   "line-icon-happy-class":
+    body: "<${1:span} class=\"icon-happy$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-happy-class"
-    body: "<${1:span} class=\"icon-happy$2\"></${1:span}>$2"
   "line-icon-hazardous-class":
+    body: "<${1:span} class=\"icon-hazardous$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-hazardous-class"
-    body: "<${1:span} class=\"icon-hazardous$2\"></${1:span}>$2"
   "line-icon-heart-class":
+    body: "<${1:span} class=\"icon-heart$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-heart-class"
-    body: "<${1:span} class=\"icon-heart$2\"></${1:span}>$2"
   "line-icon-hotairballoon-class":
+    body: "<${1:span} class=\"icon-hotairballoon$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-hotairballoon-class"
-    body: "<${1:span} class=\"icon-hotairballoon$2\"></${1:span}>$2"
   "line-icon-hourglass-class":
+    body: "<${1:span} class=\"icon-hourglass$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-hourglass-class"
-    body: "<${1:span} class=\"icon-hourglass$2\"></${1:span}>$2"
   "line-icon-key-class":
+    body: "<${1:span} class=\"icon-key$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-key-class"
-    body: "<${1:span} class=\"icon-key$2\"></${1:span}>$2"
   "line-icon-laptop-class":
+    body: "<${1:span} class=\"icon-laptop$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-laptop-class"
-    body: "<${1:span} class=\"icon-laptop$2\"></${1:span}>$2"
   "line-icon-layers-class":
+    body: "<${1:span} class=\"icon-layers$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-layers-class"
-    body: "<${1:span} class=\"icon-layers$2\"></${1:span}>$2"
   "line-icon-lifesaver-class":
+    body: "<${1:span} class=\"icon-lifesaver$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-lifesaver-class"
-    body: "<${1:span} class=\"icon-lifesaver$2\"></${1:span}>$2"
   "line-icon-lightbulb-class":
+    body: "<${1:span} class=\"icon-lightbulb$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-lightbulb-class"
-    body: "<${1:span} class=\"icon-lightbulb$2\"></${1:span}>$2"
   "line-icon-linegraph-class":
+    body: "<${1:span} class=\"icon-linegraph$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-linegraph-class"
-    body: "<${1:span} class=\"icon-linegraph$2\"></${1:span}>$2"
   "line-icon-linkedin-class":
+    body: "<${1:span} class=\"icon-linkedin$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-linkedin-class"
-    body: "<${1:span} class=\"icon-linkedin$2\"></${1:span}>$2"
   "line-icon-lock-class":
+    body: "<${1:span} class=\"icon-lock$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-lock-class"
-    body: "<${1:span} class=\"icon-lock$2\"></${1:span}>$2"
   "line-icon-magnifying-glass-class":
+    body: "<${1:span} class=\"icon-magnifying-glass$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-magnifying-glass-class"
-    body: "<${1:span} class=\"icon-magnifying-glass$2\"></${1:span}>$2"
   "line-icon-map-class":
+    body: "<${1:span} class=\"icon-map$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-map-class"
-    body: "<${1:span} class=\"icon-map$2\"></${1:span}>$2"
   "line-icon-map-pin-class":
+    body: "<${1:span} class=\"icon-map-pin$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-map-pin-class"
-    body: "<${1:span} class=\"icon-map-pin$2\"></${1:span}>$2"
   "line-icon-megaphone-class":
+    body: "<${1:span} class=\"icon-megaphone$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-megaphone-class"
-    body: "<${1:span} class=\"icon-megaphone$2\"></${1:span}>$2"
   "line-icon-mic-class":
+    body: "<${1:span} class=\"icon-mic$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-mic-class"
-    body: "<${1:span} class=\"icon-mic$2\"></${1:span}>$2"
   "line-icon-mobile-class":
+    body: "<${1:span} class=\"icon-mobile$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-mobile-class"
-    body: "<${1:span} class=\"icon-mobile$2\"></${1:span}>$2"
   "line-icon-newspaper-class":
+    body: "<${1:span} class=\"icon-newspaper$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-newspaper-class"
-    body: "<${1:span} class=\"icon-newspaper$2\"></${1:span}>$2"
   "line-icon-notebook-class":
+    body: "<${1:span} class=\"icon-notebook$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-notebook-class"
-    body: "<${1:span} class=\"icon-notebook$2\"></${1:span}>$2"
   "line-icon-paintbrush-class":
+    body: "<${1:span} class=\"icon-paintbrush$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-paintbrush-class"
-    body: "<${1:span} class=\"icon-paintbrush$2\"></${1:span}>$2"
   "line-icon-paperclip-class":
+    body: "<${1:span} class=\"icon-paperclip$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-paperclip-class"
-    body: "<${1:span} class=\"icon-paperclip$2\"></${1:span}>$2"
   "line-icon-pencil-class":
+    body: "<${1:span} class=\"icon-pencil$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-pencil-class"
-    body: "<${1:span} class=\"icon-pencil$2\"></${1:span}>$2"
   "line-icon-phone-class":
+    body: "<${1:span} class=\"icon-phone$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-phone-class"
-    body: "<${1:span} class=\"icon-phone$2\"></${1:span}>$2"
   "line-icon-picture-class":
+    body: "<${1:span} class=\"icon-picture$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-picture-class"
-    body: "<${1:span} class=\"icon-picture$2\"></${1:span}>$2"
   "line-icon-pictures-class":
+    body: "<${1:span} class=\"icon-pictures$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-pictures-class"
-    body: "<${1:span} class=\"icon-pictures$2\"></${1:span}>$2"
   "line-icon-piechart-class":
+    body: "<${1:span} class=\"icon-piechart$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-piechart-class"
-    body: "<${1:span} class=\"icon-piechart$2\"></${1:span}>$2"
   "line-icon-presentation-class":
+    body: "<${1:span} class=\"icon-presentation$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-presentation-class"
-    body: "<${1:span} class=\"icon-presentation$2\"></${1:span}>$2"
   "line-icon-pricetags-class":
+    body: "<${1:span} class=\"icon-pricetags$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-pricetags-class"
-    body: "<${1:span} class=\"icon-pricetags$2\"></${1:span}>$2"
   "line-icon-printer-class":
+    body: "<${1:span} class=\"icon-printer$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-printer-class"
-    body: "<${1:span} class=\"icon-printer$2\"></${1:span}>$2"
   "line-icon-profile-female-class":
+    body: "<${1:span} class=\"icon-profile-female$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-profile-female-class"
-    body: "<${1:span} class=\"icon-profile-female$2\"></${1:span}>$2"
   "line-icon-profile-male-class":
+    body: "<${1:span} class=\"icon-profile-male$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-profile-male-class"
-    body: "<${1:span} class=\"icon-profile-male$2\"></${1:span}>$2"
   "line-icon-puzzle-class":
+    body: "<${1:span} class=\"icon-puzzle$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-puzzle-class"
-    body: "<${1:span} class=\"icon-puzzle$2\"></${1:span}>$2"
   "line-icon-quote-class":
+    body: "<${1:span} class=\"icon-quote$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-quote-class"
-    body: "<${1:span} class=\"icon-quote$2\"></${1:span}>$2"
   "line-icon-recycle-class":
+    body: "<${1:span} class=\"icon-recycle$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-recycle-class"
-    body: "<${1:span} class=\"icon-recycle$2\"></${1:span}>$2"
   "line-icon-refresh-class":
+    body: "<${1:span} class=\"icon-refresh$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-refresh-class"
-    body: "<${1:span} class=\"icon-refresh$2\"></${1:span}>$2"
   "line-icon-ribbon-class":
+    body: "<${1:span} class=\"icon-ribbon$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-ribbon-class"
-    body: "<${1:span} class=\"icon-ribbon$2\"></${1:span}>$2"
   "line-icon-rss-class":
+    body: "<${1:span} class=\"icon-rss$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-rss-class"
-    body: "<${1:span} class=\"icon-rss$2\"></${1:span}>$2"
   "line-icon-sad-class":
+    body: "<${1:span} class=\"icon-sad$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-sad-class"
-    body: "<${1:span} class=\"icon-sad$2\"></${1:span}>$2"
   "line-icon-scissors-class":
+    body: "<${1:span} class=\"icon-scissors$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-scissors-class"
-    body: "<${1:span} class=\"icon-scissors$2\"></${1:span}>$2"
   "line-icon-scope-class":
+    body: "<${1:span} class=\"icon-scope$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-scope-class"
-    body: "<${1:span} class=\"icon-scope$2\"></${1:span}>$2"
   "line-icon-search-class":
+    body: "<${1:span} class=\"icon-search$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-search-class"
-    body: "<${1:span} class=\"icon-search$2\"></${1:span}>$2"
   "line-icon-shield-class":
+    body: "<${1:span} class=\"icon-shield$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-shield-class"
-    body: "<${1:span} class=\"icon-shield$2\"></${1:span}>$2"
   "line-icon-speedometer-class":
+    body: "<${1:span} class=\"icon-speedometer$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-speedometer-class"
-    body: "<${1:span} class=\"icon-speedometer$2\"></${1:span}>$2"
   "line-icon-strategy-class":
+    body: "<${1:span} class=\"icon-strategy$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-strategy-class"
-    body: "<${1:span} class=\"icon-strategy$2\"></${1:span}>$2"
   "line-icon-streetsign-class":
+    body: "<${1:span} class=\"icon-streetsign$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-streetsign-class"
-    body: "<${1:span} class=\"icon-streetsign$2\"></${1:span}>$2"
   "line-icon-tablet-class":
+    body: "<${1:span} class=\"icon-tablet$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-tablet-class"
-    body: "<${1:span} class=\"icon-tablet$2\"></${1:span}>$2"
   "line-icon-target-class":
+    body: "<${1:span} class=\"icon-target$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-target-class"
-    body: "<${1:span} class=\"icon-target$2\"></${1:span}>$2"
   "line-icon-telescope-class":
+    body: "<${1:span} class=\"icon-telescope$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-telescope-class"
-    body: "<${1:span} class=\"icon-telescope$2\"></${1:span}>$2"
   "line-icon-toolbox-class":
+    body: "<${1:span} class=\"icon-toolbox$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-toolbox-class"
-    body: "<${1:span} class=\"icon-toolbox$2\"></${1:span}>$2"
   "line-icon-tools-2-class":
+    body: "<${1:span} class=\"icon-tools-2$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-tools-2-class"
-    body: "<${1:span} class=\"icon-tools-2$2\"></${1:span}>$2"
   "line-icon-tools-class":
+    body: "<${1:span} class=\"icon-tools$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-tools-class"
-    body: "<${1:span} class=\"icon-tools$2\"></${1:span}>$2"
   "line-icon-trophy-class":
+    body: "<${1:span} class=\"icon-trophy$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-trophy-class"
-    body: "<${1:span} class=\"icon-trophy$2\"></${1:span}>$2"
   "line-icon-tumblr-class":
+    body: "<${1:span} class=\"icon-tumblr$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-tumblr-class"
-    body: "<${1:span} class=\"icon-tumblr$2\"></${1:span}>$2"
   "line-icon-twitter-class":
+    body: "<${1:span} class=\"icon-twitter$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-twitter-class"
-    body: "<${1:span} class=\"icon-twitter$2\"></${1:span}>$2"
   "line-icon-upload-class":
+    body: "<${1:span} class=\"icon-upload$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-upload-class"
-    body: "<${1:span} class=\"icon-upload$2\"></${1:span}>$2"
   "line-icon-video-class":
+    body: "<${1:span} class=\"icon-video$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-video-class"
-    body: "<${1:span} class=\"icon-video$2\"></${1:span}>$2"
   "line-icon-wallet-class":
+    body: "<${1:span} class=\"icon-wallet$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-wallet-class"
-    body: "<${1:span} class=\"icon-wallet$2\"></${1:span}>$2"
   "line-icon-wine-class":
+    body: "<${1:span} class=\"icon-wine$2\"></${1:span}>$2"
+    description: "<span class>"
     prefix: "line-icon-wine-class"
-    body: "<${1:span} class=\"icon-wine$2\"></${1:span}>$2"


Bug fix for group listing when logged out

--- client/group.coffee
+++ client/group.coffee
@@ -49,7 +49,7 @@
 Template.registerHelper 'groupsMine', ->
   Groups.find
     $or: [
-      name: $in: (unescapeGroup group for own group, roles of Meteor.user().roles ? {} when 'read' in roles)
+      name: $in: (unescapeGroup group for own group, roles of Meteor.user()?.roles ? {} when 'read' in roles)
     ,
       anonymous: 'read'
     ]
@@ -58,7 +58,7 @@

 Template.registerHelper 'groupsOther', ->
   Groups.find
-    name: $nin: (unescapeGroup group for own group, roles of Meteor.user().roles ? {} when 'read' in roles)
+    name: $nin: (unescapeGroup group for own group, roles of Meteor.user()?.roles ? {} when 'read' in roles)
     anonymous: $ne: 'read'
   ,
     sort: [['name', 'asc']]


fix typos

fix typos

--- content_scripts/hud.coffee
+++ content_scripts/hud.coffee
@@ -113,8 +113,8 @@
   unfocusIfFocused: ->
     document.activeElement.blur() if document.activeElement == @hudUI?.iframeElement

-  clearFocus: ->
-    if @hudUI?.showing
+  giveUpFocus: ->
+    if @hudUI.showing
       @hudUI.iframeElement.blur()
       window.focus()


Check for visibility change when mounting instead of polling DOM

--- src/editor-component.coffee
+++ src/editor-component.coffee
@@ -178,7 +178,7 @@
     @subscribe scrollbarStyle.changes, @refreshScrollbars

     @domPollingIntervalId = setInterval(@pollDOM, @domPollingInterval)
-    @pollDOM()
+    @checkForVisibilityChange()

   componentWillUnmount: ->
     @props.parentView.trigger 'editor:will-be-removed', [@props.parentView]


Combine all updates that occur in an animation frame

--- src/editor-component.coffee
+++ src/editor-component.coffee
@@ -25,6 +25,8 @@
   pendingScrollTop: null
   pendingScrollLeft: null
   selectOnMouseMove: false
+  updatesPaused: false
+  updateRequestedWhilePaused: false
   updateRequested: false
   cursorsMoved: false
   selectionChanged: false
@@ -203,20 +205,23 @@
     @remeasureCharacterWidthsIfNeeded(prevState)

   requestUpdate: ->
-    if @performSyncUpdates ? EditorComponent.performSyncUpdates
-      @forceUpdate()
-    else unless @updateRequested
-      @updateRequested = true
-      process.nextTick =>
-        @updateRequested = false
-        @forceUpdate() if @isMounted()
+    if @updatesPaused
+      @updateRequestedWhilePaused = true
+    else
+      if @performSyncUpdates ? EditorComponent.performSyncUpdates
+        @forceUpdate()
+      else unless @updateRequested
+        @updateRequested = true
+        process.nextTick =>
+          @updateRequested = false
+          @forceUpdate() if @isMounted()

   requestAnimationFrame: (fn) ->
-    prevPerformSyncUpdates = @performSyncUpdates
-    @performSyncUpdates = true
     requestAnimationFrame =>
+      @updatesPaused = true
       fn()
-      @performSyncUpdates = prevPerformSyncUpdates
+      @updatesPaused = false
+      @forceUpdate() if @updateRequestedWhilePaused

   getRenderedRowRange: ->
     {editor, lineOverdrawMargin} = @props


Remove editor-colors class from line numbers

--- src/gutter-component.coffee
+++ src/gutter-component.coffee
@@ -131,7 +131,7 @@
       style = "visibility: hidden;"
     innerHTML = @buildLineNumberInnerHTML(bufferRow, softWrapped, maxLineNumberDigits)

-    "
#{innerHTML}
"
+    "
#{innerHTML}
"

   buildLineNumberInnerHTML: (bufferRow, softWrapped, maxLineNumberDigits) ->
     if softWrapped


Remove dialog positioning workaround for iOS<5, and improve iOS dialog display.

--- lib/ST/DialogView.coffee
+++ lib/ST/DialogView.coffee
@@ -5,7 +5,6 @@
     @init()
     @_element.attr 'id', 'dialog'
     @_element.hide()
-    @_element.makeFixed Math.round($(window).width() / 2), 0
     @_element.mousedown (e) -> e.stopPropagation()
     @_element.appendTo document.body
     @load()
@@ -60,16 +59,20 @@
   @method 'showBlanker', ->
     # Add blanker div if it doesn't already exist
     if $('#blanker').length < 1
-      $('body').append $('

').css('opacity', 0).click (e) -> e.stopPropagation()
+      blanker = $('

')
+      blanker.css 'opacity', 0
+      blanker.click (e) -> e.stopPropagation()
+      $('body').append blanker
+      blanker.bind 'touchstart touchmove touchend', (e) -> e.preventDefault()

       # Fade blanker in
       if $.browser.webkit
-        $('#blanker').css('height', $(document).height())
+        blanker.css('height', $(document).height())
             .css('width', $(document).width())
             .css('-webkit-transition', 'opacity 100ms linear')
             .css('opacity', 0.6)
       else
-        $('#blanker').show().css('opacity', 0).animate {opacity: 0.6}, 100, 'linear'
+        blanker.show().animate {opacity: 0.6}, 100, 'linear'
     else
       # Prevent currently visible blanker from hiding
       $('#blanker').stop().css('opacity', 0.6)
@@ -79,17 +82,22 @@
     blanker = $ '#blanker'
     if blanker.length > 0
       # Fade blanker out
-      blanker.animate {opacity : 0}, 300, 'linear', ->
-        blanker.remove()
+      if $.browser.webkit
+        blanker.css('-webkit-transition', 'opacity 100ms linear')
+          .css('opacity', 0.0)
+          .bind 'webkitTransitionEnd', ->
+            $(this).unbind('webkitTransitionEnd')
+            blanker.remove()
+      else
+        blanker.animate {opacity : 0}, 300, 'linear', -> blanker.remove()

   @method 'showDialog', ->
     self = this
     if $.browser.webkit
-      offset = if ST.touch() then window.pageYOffset else 0
-      @_element.css('top', offset - @_element.height())
+      @_element.css('top', 0 - @_element.height())
           .show()
           .css('-webkit-transition', 'top 200ms ease-in')
-          .css('top', offset)
+          .css('top', 0)
           .bind 'webkitTransitionEnd', ->
             $(this).css('-webkit-transition', '')
                 .unbind('webkitTransitionEnd')
@@ -105,9 +113,8 @@

   @method 'hideDialog', (callback) ->
     if $.browser.webkit
-      offset = if ST.touch() then window.pageYOffset else 0
       @_element.css('-webkit-transition', 'top 200ms ease-in')
-          .css('top', offset - @_element.height())
+          .css('top', 0 - @_element.height())
           .bind 'webkitTransitionEnd', ->
             $(this).unbind('webkitTransitionEnd')
             callback() if callback


Fixed Enumerable#unique, added callback support for Enumerable#min to match Enumerable#max.

--- lib/ST/Enumerable.coffee
+++ lib/ST/Enumerable.coffee
@@ -97,8 +97,7 @@
   @method 'unique', ->
     list = ST.List.create()
     @each (item) ->
-      unless list.array.indexOf(item) >= 0
-        list.add item
+      list.add item unless list.has item
     list

   # Returns a new list containing only items where callback returns true
@@ -118,9 +117,11 @@
     list

   # Returns lowest value
-  @method 'min', ->
+  @method 'min', (fn) ->
+    fn = fn && ST.toProc fn
     value = null
     @each (item) ->
+      item = fn item if fn
       value = item if item < value || value == null
     value