Added SoundCloud API for search

Added SoundCloud API for search

--- coffee/_TrackSource.class.coffee
+++ coffee/_TrackSource.class.coffee
@@ -3,19 +3,24 @@
 class TrackSource

     @search: (keywords, success) ->
-        tracks_all = {}
+        tracks_all = {
+          "itunes": []
+          "lastfm": []
+          "soundcloud": []
+        }

         mashTracks = ->
-            tracks_all = tracks_all['itunes'].concat tracks_all['lastfm']
+            tracks_allconcat = tracks_all['itunes'].concat tracks_all['lastfm'], tracks_all['soundcloud']

             tracks_deduplicated = []
             tracks_hash = []
-            $.each tracks_all, (i, track) ->
-                if track.artist and track.title
-                    track_hash = track.artist.toLowerCase() + '___' + track.title.toLowerCase()
-                    if track_hash not in tracks_hash
-                        tracks_deduplicated.push(track)
-                        tracks_hash.push(track_hash)
+            $.each tracks_allconcat, (i, track) ->
+                if track
+                    if track.artist and track.title
+                        track_hash = track.artist.toLowerCase() + '___' + track.title.toLowerCase()
+                        if track_hash not in tracks_hash
+                            tracks_deduplicated.push(track)
+                            tracks_hash.push(track_hash)
             success? tracks_deduplicated

         # itunes
@@ -33,7 +38,7 @@
                             cover_url_medium: track.artworkUrl60
                             cover_url_large: track.artworkUrl100
                 tracks_all['itunes'] = tracks
-                if Object.keys(tracks_all).length == 2
+                if Object.keys(tracks_all).length > 1
                     mashTracks()

         # last.fm
@@ -60,8 +65,30 @@
                             cover_url_medium: cover_url_medium
                             cover_url_large: cover_url_large
                 tracks_all['lastfm'] = tracks
-                if Object.keys(tracks_all).length == 2
+                if Object.keys(tracks_all).length > 1
                     mashTracks()
+
+        # Soundcloud
+        request
+            url: 'https://api.soundcloud.com/tracks.json?client_id=dead160b6295b98e4078ea51d07d4ed2&q=' + encodeURIComponent(keywords)
+            json: true
+        , (error, response, data) ->
+            tracks = []
+            $.each data, (i, track) ->
+                if track
+                    trackNameExploded = track.title.split(" - ")
+                    coverPhoto = track.artwork_url
+                    coverPhoto = 'images/cover_default_large.png' if !track.artwork_url
+                    console.log(trackNameExploded);
+                    tracks.push
+                        title: trackNameExploded[0]
+                        artist: trackNameExploded[1]
+                        cover_url_medium: coverPhoto
+                        cover_url_large: coverPhoto
+
+            tracks_all['soundcloud'] = tracks
+            if Object.keys(tracks_all).length > 1
+              mashTracks()

     @topTracks: (success) ->


Updated terminals to include more documentation.

--- coffeequate/src/terminals.coffee
+++ coffeequate/src/terminals.coffee
@@ -22,6 +22,12 @@
 		# Rational * Rational -> Rational
 		# Rational * Float -> Float
 		# Float * Float -> Float
+
+		# Make a new Constant
+		#
+		# @param numerator [String]
+		# @param denominator [String]
+		# @param mode [String] Optional. Can be rational or float.
 		constructor: (@numerator, @denominator=1, @mode="rational") ->
 			@cmp = -6

@@ -36,14 +42,23 @@
 					@numerator /= @denominator
 					@denominator = 1

+		# Evaluate the constant to be a float
+		#
+		# @return [Float] A float representation of this Constant
 		evaluate: ->
 			parseFloat(@numerator/@denominator)

+		#Deep copy this Constant
+		#
+		#@return [Constant] A copy of this Constant
 		copy: ->
 			return new Constant(@numerator, @denominator, @mode)

+		#Compares this constant with another constant
+		#
+		#@param b [Constant]
+		#@return [Integer] returns an integer based on the comparison result (negative, neutral or positive)
 		compareSameType: (b) ->
-			# Compare this object with another of the same type.
 			if @evaluate() < b.evaluate()
 				return -1
 			else if @evaluate() == b.evaluate()
@@ -51,8 +66,11 @@
 			else
 				return 1

+		# Multiplies by another constant and returns the resulting Constant
+		#
+		# @param b [Constant]
+		# return [Constant] The multiple of this and b
 		mul: (b) ->
-			# Multiply by another constant and return the result.
 			if @mode == b.mode
 				newMode = @mode
 			else
@@ -60,17 +78,20 @@

 			return new Constant(@numerator * b.numerator, @denominator * b.denominator, newMode)

+		# Adds to another constant and returns the resulting Constant.
+		# @param b [Constant]
+		# @return [Constant] The addition of this and b
 		add: (b) ->
-			# Add another constant and return the result.
 			if @mode == b.mode
 				newMode = @mode
 			else
 				newMode = "float"

 			return new Constant(b.denominator * @numerator + @denominator * b.numerator, @denominator * b.denominator, newMode)
-
+		# This constant to the power of another constant
+		# @param b [Constant]
+		# @return [Constant] Returns a constant of base this to power b
 		pow: (b) ->
-			# Put this constant to the power of another constant and return the result.
 			if @mode == "rational"
 				if b.mode == "rational"
 					flip = false # Whether to invert the fraction (for negative powers).
@@ -111,21 +132,32 @@
 				# Also returning a float.
 				return new Constant(Math.pow(@evaluate(), b.evaluate()), 1, "float")

+		# Tests the equality between this Constant and another Constant.
+		# @param b [Constant]
+		# @return [Boolean] Whether or not the two Constants are equal
 		equals: (b) ->
-			# Test equality between this object and another.
 			unless b instanceof Constant
 				return false
 			return @evaluate() == b.evaluate()

+		# Does nothing - included for API parity
+		# @param replacements [???]
 		replaceVariables: (replacements) ->
 			@copy() # Does nothing - this is a constant.

 		getAllVariables: ->
 			[]

+		# Substitute values into the constant - does nothing as this is a constant;
+		# Included for API parity
+		#
+		#@param substitutions [Object] A map of variable labels to their values. Values can be integers, Expressions, Terminals or BasicNodes
+		#@param uncertaintySubstitutions [???] I don't know how this works
 		sub: (substitutions, uncertaintySubstitutions) ->
 			@copy()

+		# Gets greatest common divisor and divides out to simplify Constant
+		#
 		simplifyInPlace: ->
 			# Get the greatest common divisor.
 			a = @numerator
@@ -140,29 +172,52 @@
 			@denominator /= gcd
 			@denominator = Math.round(@denominator*10)/10

+		# Simplify this constant
+		#
+		# @return [Constant] A simplified copy of the current Constant
 		simplify: ->
 			constant = @copy()
 			constant.simplifyInPlace()
 			return constant

+		# Expands the constant
+		# @return [Constant] A simplified constant
 		expand: ->
 			@copy()

+		# Expands and simplifies
+		# @return [Constant] A simplified and expanded constant
 		expandAndSimplify: ->
 			@simplify()

+		# Substitutes
 		substituteExpression: (sourceExpression, variable, equivalencies) ->
 			[@copy()]

+		# Gets uncertainty of the Constant
+		#
+		# @return [Constant] A constant of 0
 		getUncertainty: ->
 			new Constant(0)

+		# Gets the units of the Constant (does nothing since Constant)
+		#
+		# @return [null] null
 		getVariableUnits: ->
 			null

+		#Sets units of the Constant (does nothing)
+		#
+		# @param variable [???]
+		# @param equivalencies [???]
+		# @param units [String]
+		# @return null
 		setVariableUnits: (variable, equivalencies, units) ->
 			null

+		# Creates a drawing node to export Constant for viewing.
+		#
+		# @return [FractionNode, NumberNode] A drawing node for viewing.
 		toDrawingNode: ->
 			NumberNode = prettyRender.Number
 			FractionNode = prettyRender.Fraction
@@ -173,14 +228,29 @@
 		differentiate: (variable) ->
 			return new Constant(0)

+
+	# A class to represent symbolic constants in the equation tree; for e.g. Ï€
+	#
 	class SymbolicConstant extends Terminal
+
 		# Symbolic constants in the equation tree, e.g. Ï€
+		# Creates a new SymbolicConstant.
+		# @param label [String]
+		# @param value [Integer, Float] Optional. Default is null.
+		# @param units [String] Optional. Default is null.
 		constructor: (@label, @value=null, @units=null) ->
 			@cmp = -5

+		# Returns a deep copy of the SymbolicConstant
+		#
+		# @return [SymbolicConstant] Returns a new copy of the Symbolic Constant
 		copy: ->
 			return new SymbolicConstant(@label, @value, @units)

+		# Compares this SymbolicConstant with another SymbolicConstant.
+		#
+		# @param b [SymbolicConstant]
+		# @return [Integer 0,1,2] Returns the difference 'discriminator' of the two SymbolicConstants
 		compareSameType: (b) ->
 			# Compare this object with another of the same type.
 			if @label < b.label
@@ -190,62 +260,121 @@
 			else
 				return 1

+		# Evaluates the SymbolicConstant
+		# @return [Integer, Float] Returns the current value of the SymbolicConstant
 		evaluate: ->
 			@value

+		# Checks equality of this and another SymbolicConstant
+		#
+		# @param b [SymbolicConstant]
+		# @return [Boolean] Whether or not the two SymbolicConstants are equal
 		equals: (b) ->
 			unless b instanceof SymbolicConstant
 				return false
 			return @label == b.label and @value == b.value

+		# Does nothing - included for API parity
+		#
+		# @params replacements [???]
+		# @return [SymbolicConstant]
 		replaceVariables: (replacements) ->
 			@copy() # Does nothing - this is a constant.

+		#Does nothing??? Matt, please check
 		getAllVariables: ->
 			[]

+
+		# Substitute values into the SymbolicConstant - does nothing as this is a SymbolicConstant;
+		# Included for API parity
+		#
+		#@param substitutions [Object] A map of variable labels to their values. Values can be integers, Expressions, Terminals or BasicNodes
+		#@param uncertaintySubstitutions [???] I don't know how this works
 		sub: (substitutions, uncertaintySubstitutions, equivalencies=null, assumeZeroUncertainty=false, evaluateSymbolicConstants=false) ->
 			unless evaluateSymbolicConstants
 				return @copy()
 			if @label of constants
 				return new Constant(constants[@label])

+		# Simplify the SymbolicConstant
+		#
+		# @return [SymbolicConstant] Returns a deep copy of the SymbolicConstant
 		simplify: ->
 			@copy()

+		# Expand the SymbolicConstant
+		#
+		# @return [SymbolicConstant] Returns a deep copy of the SymbolicConstant
 		expand: ->
 			@copy()

+		# Expand and simplify the SymbolicConstant
+		#
+		# @return [SymbolicConstant] Returns a deep copy of the SymbolicConstant
 		expandAndSimplify: ->
 			@copy()

+		# Substitutes an expression into the SymbolicConstant
+		#
+		# @param sourceExpression [Expression]
+		# @param variable [???]
+		# @param equivalencies []
 		substituteExpression: (sourceExpression, variable, equivalencies) ->
 			[@copy()]

+		# Gets the uncertainty of the SymbolicConstant
+		#
+		# @return [Constant] Returns a Constant of 0
 		getUncertainty: ->
 			new Constant(0)

+		# Gets the units of the SymbolicConstant i.e. null
+		# @return null
 		getVariableUnits: ->
 			null

+		# Sets the units of the SymbolicConstant
+		# @param variable
+		# @param equivalencies
+		# @param units
 		setVariableUnits: (variable, equivalencies, units) ->
 			null

+		# Creates a DrawingNode of the SymbolicConstant to be viewed
+		#
+		# @return [VariableNode] Returns a prettyRendered DrawingNode of the SymbolicConstant
 		toDrawingNode: ->
 			VariableNode = prettyRender.Variable
 			return new VariableNode(@label, "constant symbolic-constant")

+		# Differentiates the SymbolicConstant
+		# @param variable [???]
+		# @return [Constant] Returns a Constant of 0
 		differentiate: (variable) ->
 			return new Constant(0)

+	# A class to represent Variables in the Equation tree; e.g. 'm'
 	class Variable extends Terminal
 		# Variables in the equation tree, e.g. m
+
+		# Make a new Variable
+		#
+		# @param label [String]
+		# @param units [???] Optional. Defaults to null.
 		constructor: (@label, @units=null) ->
 			@cmp = -4

+		# Creates a deep copy of the current Variable
+		#
+		# @return [Variable] Returns a deep copy of the current Variable
 		copy: ->
 			return new Variable(@label, @units)

+		# Compares this Variable with another Variable
+		#
+		# @param b [Variable] The other Variable
+		# @return [Integer -1, 0, 1] Returns an integer to indicate less than, equal to or greater than the other Variable
 		compareSameType: (b) ->
 			# Compare this object with another of the same type.
 			if @label < b.label
@@ -255,6 +384,10 @@
 			else
 				return 1

+		# Checks equality of this Variable and another Variable
+		#
+		# @param b [Variable] The other variable
+		# @param equivalencies [???] Optional.
 		equals: (b, equivalencies=null) ->
 			# Check equality between this and some other object.
 			unless b instanceof Variable
@@ -265,15 +398,22 @@
 			else
 				return b.label == @label

+		# Replaces the variables with new variables (MATT I DON'T GET THIS ONE)
 		replaceVariables: (replacements) ->
 			copy = @copy()
 			if @label of replacements
 				copy.label = replacements[@label]
 			return copy

+		# Gets all variables
+		# @return [List of Strings] Returns the variable label
 		getAllVariables: ->
 			[@label]

+		# Substitutes values into the Variable
+		#
+		# @param [???] substitutions
+		# @param [???] uncertaintySubstitutions
 		sub: (substitutions, uncertaintySubstitutions) ->
 			if @label of substitutions
 				substitute = substitutions[@label]
@@ -284,6 +424,13 @@
 			else
 				return @copy()

+		# Substitutes an Expression into the Variable
+		#
+		# @param sourceExpression [sourceExpression] The source expression to be used.
+		# @param variable [???]
+		# @param equivalencies [???] Optional.
+		# @param eliminate [???] Optional.
+		# @return [Variable] Returns the resulting Variable from the substitution
 		substituteExpression: (sourceExpression, variable, equivalencies=null, eliminate=false) ->
 			# Replace all instances of a variable with an expression.

@@ -303,9 +450,17 @@
 			else
 				return [@copy()]

+		# Gets the uncertainty of the Variable
+		#
+		# @return [???] IDK how this one works either TBH
 		getUncertainty: ->
 			new Uncertainty(@label)

+		# Gets the units of the Variable
+		#
+		# @param variable [???]
+		# @param equivalencies [???]
+		# @return [units] The current units of the Variable
 		getVariableUnits: (variable, equivalencies) ->
 			if equivalencies?
 				if @label in equivalencies.get(variable)
@@ -314,6 +469,11 @@
 				return @units
 			return null

+		# Sets the units for the Variable
+		#
+		# @param variable [???]
+		# @param equivalencies [???]
+		# @param units [???]
 		setVariableUnits: (variable, equivalencies, units) ->
 			if equivalencies?
 				if @label in equivalencies.get(variable)
@@ -321,26 +481,47 @@
 			else if @label == variable
 				@units = units

-		simplify: ->
-			@copy()
-
+		# Simplifies the Variable
+		#
+		# @return [Variable] A deep copy of the Variable
+ 		simplify: ->
+			@copy()
+
+		# Expand the Variable
+		#
+		# @return [Variable] A deep copy of the Variable
 		expand: ->
 			@copy()

+		# Expands and simplifies the Variable
+		#
+		#@return [Variable] A deep copy of the Variable
 		expandAndSimplify: ->
 			@copy()

+		# Returns a DrawingNode of the Variable to be viewed
+		#
+		# @return [VariableNode] A DrawingNode version of the variable
 		toDrawingNode: ->
 			VariableNode = prettyRender.Variable
 			return new VariableNode(@label)

+		# Differentiates the variable
+		#
+		# @param variable [Variable]
+		# @return [Constant] Returns a constant of value either 1 or 0 (Mitchell: not sure how this actually works)
 		differentiate: (variable) ->
 			if variable == @label
 				return new Constant(1)
 			return new Constant(0)

+	# Defines an Uncertainty object in the equation tree; for e.g. sigma_m
+	# ??? I know nothing about this object really, it's going to be very poorly documented and needs to be edited
 	class Uncertainty extends Terminal
-		# Uncertainty in the equation tree, e.g. sigma_m
+
+		#Creates an Uncertainty (that sounds really beautiful doesn't it?)
+		#
+		# @param label [String] Label of the Uncertainty object
 		constructor: (@label) ->
 			# Matt: what do I do here?
 			@cmp = -4.5
@@ -348,8 +529,11 @@
 		copy: ->
 			return new Uncertainty(@label)

+		#Compares the value of this Uncertainty and another Uncertainty
+		#
+		# @param b [Uncertainty]
+		# @return [Integer -1, 0, 1]
 		compareSameType: (b) ->
-			# Compare this object with another of the same type.
 			if @label < b.label
 				return -1
 			else if @label == b.label
@@ -412,11 +596,10 @@
 		toDrawingNode: ->
 			UncertaintyNode = prettyRender.Uncertainty
 			return new UncertaintyNode(@label)
-
+		# Don't differentiate uncertainties. It won't work. (Throws error 'Can't differentiate uncertainties!')
 		differentiate: (variable) ->
 			throw new Error("Can't differentiate uncertainties!")

-
 	return {

 		Terminal: Terminal


workaround for lack of DOM element .remove in IE

--- services/web/public/coffee/ide/pdfng/directives/pdfHighlights.coffee
+++ services/web/public/coffee/ide/pdfng/directives/pdfHighlights.coffee
@@ -24,6 +24,6 @@

 			clearHighlights: () ->
 				for h in @highlightElements
-					h?.remove()
+					h?.parentNode.removeChild(h)
 				@highlightElements = []
 	]


Add benchmark for empty() vs innerHTML = ''

--- benchmark/benchmark-suite.coffee
+++ benchmark/benchmark-suite.coffee
@@ -59,6 +59,23 @@
         editor.insertText('"')
         editor.backspace()

+    describe "empty-vs-set-innerHTML.", ->
+      [firstRow, lastRow] = []
+      beforeEach ->
+        firstRow = editor.getFirstVisibleScreenRow()
+        lastRow = editor.getLastVisibleScreenRow()
+
+      benchmark "build-gutter-html.", 1000, ->
+        editor.gutter.renderLineNumbers(null, firstRow, lastRow)
+
+      benchmark "set-innerHTML.", 1000, ->
+        editor.gutter.renderLineNumbers(null, firstRow, lastRow)
+        editor.gutter.lineNumbers[0].innerHtml = ''
+
+      benchmark "empty.", 1000, ->
+        editor.gutter.renderLineNumbers(null, firstRow, lastRow)
+        editor.gutter.lineNumbers.empty()
+
     describe "positionLeftForLineAndColumn.", ->
       line = null
       beforeEach ->
@@ -95,7 +112,7 @@
       benchmark "resetDisplay", 50, ->
         editor.resetDisplay()

-      fbenchmark "htmlForScreenRows", 1000, ->
+      benchmark "htmlForScreenRows", 1000, ->
         lastRow = editor.getLastScreenRow()
         editor.htmlForScreenRows(0, lastRow)


fixes #78

fixes #78

--- app/assets/javascripts/router.coffee
+++ app/assets/javascripts/router.coffee
@@ -43,11 +43,15 @@
     @$mainContainer = $("#main-container")

+
+  ### Routes ###
+
   user : (date) ->

     userModel = new UserViewModel(date : date)
     @showReport(userModel)
     @changeActiveNavbarItem("/")
+    @changeNavbarDate(date)
     @changeTitle("Me")

@@ -56,6 +60,7 @@
     projectModel = new ProjectViewModel(date : date)
     @showReport(projectModel)
     @changeActiveNavbarItem("/project")
+    @changeNavbarDate(date)
     @changeTitle("Project")

@@ -64,6 +69,7 @@
     teamModel = new TeamViewModel(date : date)
     @showReport(teamModel)
     @changeActiveNavbarItem("/team")
+    @changeNavbarDate(date)
     @changeTitle("Team")

@@ -72,6 +78,7 @@
     logTimeModel = new LogTimeModel()
     @changeView(new LogTimeLocalView(model : logTimeModel))
     @changeActiveNavbarItem("/log")
+    @changeNavbarDate()
     @changeTitle("Log time")

@@ -80,6 +87,7 @@
     logTimeModel = new LogTimeModel({repositoryId, issueNumber})
     @changeView(new LogTimeGithubView(model : logTimeModel))
     @changeActiveNavbarItem("/log")
+    @changeNavbarDate()
     @changeTitle("Log time")

@@ -87,6 +95,7 @@

     @changeView(new AdminPanelView())
     @changeActiveNavbarItem("/admin")
+    @changeNavbarDate()
     @changeTitle("Admin")

@@ -98,9 +107,12 @@
     @changeView(spinnerView, userSettingsView)
     userSettingsModel.fetch()
     @changeActiveNavbarItem()
+    @changeNavbarDate()
     @changeTitle("User Settings")

+
+  ### Helpers ###

   showReport : (model) ->

@@ -143,9 +155,29 @@

     $navbar = $("#main-nav")
     $navbar.find(".active").removeClass("active")
+
+    if url == "/"
+      url = "/me"
+
     if url
-      $navbar.find("a[href=\"#{url}\"]").closest("li").addClass("active")
-
+      $navbar.find("a[href^=\"#{url}\"]").closest("li").addClass("active")
+
+    return
+
+
+  changeNavbarDate : (date) ->
+
+    $navbarItems = $("#main-nav li").slice(0, 3)
+    $navbarItems.each(->
+      $a = $(this).find("a")
+      newHref = $a.attr("href").replace(/^(\/[a-z]+)(.*)$/, (full, match1, part2) ->
+        if date?
+          "#{match1}/#{date}"
+        else
+          match1
+      )
+      $a.attr("href", newHref)
+    )
     return


[FSPersistorManager] fix the stream opening for node10+

Attaching a `readable` listener causes the stream to hang otherwise.

Signed-off-by: Jakob Ackermann <caa3e4f01d56b797520cbfed54bdab4c5e3b0ebb@outlook.com>

--- app/coffee/FSPersistorManager.coffee
+++ app/coffee/FSPersistorManager.coffee
@@ -48,10 +48,10 @@
     fs.open "#{location}/#{filteredName}", 'r', (err, fd) ->
       if err?
         logger.err err:err, location:location, filteredName:name, "Error reading from file"
-        if err.code == 'ENOENT'
-          return callback new Errors.NotFoundError(err.message), null
-        else
-          return callback err, null
+      if err.code == 'ENOENT'
+        return callback new Errors.NotFoundError(err.message), null
+      else
+        return callback err, null
       opts.fd = fd
       sourceStream = fs.createReadStream null, opts
       return callback null, sourceStream


ensure_element_array(): Flatten nested arrays.

--- client/utils.coffee
+++ client/utils.coffee
@@ -6,9 +6,12 @@
   if not $.isArray x
     return x
   r = []
-  for i in x
-    if $.isArray i
-      r = $.merge r, i
-    else
-      r.push i
+  add_items = (x) ->
+    for i in x
+      if i
+        if $.isArray i
+          add_items i
+        else
+          r.push i
+  add_items x
   r


fix typo pointed out by @markokoleznik :D

--- lib/agda-mode.coffee
+++ lib/agda-mode.coffee
@@ -9,7 +9,7 @@
             default: ''
         libraryPath:
             title: 'Libraries'
-            description: 'paths to include (such as agda-stdlib), eperate with comma'
+            description: 'paths to include (such as agda-stdlib), seperate with comma'
             type: 'array'
             default: []
             items:


Fix all notes

Fix all notes

--- app/controllers/notebook.item.coffee
+++ app/controllers/notebook.item.coffee
@@ -14,8 +14,10 @@

   constructor: ->
     super
-    #@notebook.bind "changeNotebook", @changeNotebook
-    #@notebook.bind "update", @update
+    if @notebook.id isnt "all"
+      @notebook.bind "changeNotebook", @changeNotebook
+      @notebook.bind "update", @update
+

   expand: (e) =>
     # Categories
@@ -23,6 +25,9 @@
       Notebook.trigger("changeNotebook", {id: @notebook.id, category: $(e.target).attr("data-category")})
     else
       Notebook.trigger("changeNotebook", {id: @notebook.id, category: "all"})
+
+    # Hacky, but whatever.
+    @changeNotebook({id: "all", category: "all"}) if @notebook.id is "all"

   toggleMore: (e) =>
     e.preventDefault()


Policies page edits (#3340)

Policies page edits (#3340)

* I've edited this page, and included a note on timelines in the relevant sections

* Policies page with second round of edits from Meg incorporated

--- app/pages/lab/help/lab-policies.cjsx
+++ app/pages/lab/help/lab-policies.cjsx
@@ -5,52 +5,64 @@
 counterpart.registerTranslations 'en',
   policiesPage:
     content: '''
-      ## Zooniverse Project Builder Policies, or So You Want To Launch a Project?
+      ## How to Launch your Project and Zooniverse Policies
+      This page is for people who have used our Project Builder, and would now like to share their project with the Zooniverse community or their own crowd of collaborators or volunteers.
+

-      This page is for those who have used the builder tools to construct a project they're happy with, and are now ready to launch it to the wider world. The first thing to understand is that there are three modes which a live project can be in - _Public Projects_, _Review Projects_, and _Zooniverse Projects_:
+      ### How to Launch your Project
+      A live project can exist in three different states, it may be a;
+      1. Public Project
+      2. Review Project
+      3. Zooniverse Project

-      Firstly, **Public Projects**.
+      Each of these project states is discussed below.
+

-      Got your own crowd? Want only members of your collaboration to sort through your data? Then you can simply share the URL with those you want to take part. There is a limit of 10,000 subjects per user - [contact us](/about/contact) if you'd like to host more. NOTE: This upload limit applies to all users initially.
+      **1. Public Project**
+      Do you have your own crowd of volunteers, or only want members of your collaboration to classify your data? You can make your project public simply by sharing your project’s URL with those you want to take part.

-      _If you're planning a big public launch, or you think your project might attract significant attention, you should still [let us know](/about/contact) so we can support you properly. Otherwise you can get on with using your project._
+      _If you're planning a big public launch, or you think your project might attract significant attention, you should [let us know](/about/contact) so we can support you properly. Please note, there is a limit of 10,000 subjects per user - [contact us](/about/contact) if you'd like to host more._
+

-      For project teams who want to work with the established, ever-keen Zooniverse community, the first step is to turn your project into a **Review Project**. You can do this from the project builder, under the "Visibility" section. Clicking the "Apply for review" button will allow the Zooniverse team to review your project - this is just a quick check to make sure it complies with our rules (see below). Once your project passes this step, the Zooniverse team will disseminate your project link to volunteers who have opted in to help test new projects and provide feedback.
+      **2. Review Project**
+      If you would like the Zooniverse volunteer community to contribute to your project, the first step is to apply for review. You can do this in the project builder by clicking the “Apply for review” button within the “Visibility” section. At this point, your project is considered a “Review Project”.

-      _During the review phase, you should be checking for suggestions and comments on the project's Talk discussion pages in addition to reviewing the formal feedback from the volunteer project testers that will be provided by the Zooniverse team. Technical issues can be reported to us via [the GitHub repository](https://github.com/zooniverse/Panoptes-Front-End), but you will need to consider carefully comments on workflow design. Remember to look at the results produced during this review phase too - are they of sufficient quality for your research? If not, then a redesign or a rethink is in order; go back, make changes to your project and then submit it for review once again._
+      Your project will then be subject to two stages of review – firstly, the Zooniverse team will check your project complies with Zooniverse rules (see below). Typically, we will respond to you within two weeks. If successful, your project will then be shared with a group of volunteer project testers who will provide feedback. We will contact you when a slot becomes available for volunteer review (this currently takes one to four weeks). Once your project has been shared with our volunteer project testers, the majority of project feedback is typically received within a week. This will be made immediately accessible to you.

-      Following a successful review, you should have a final version of the workflow that you're confident will produce useful results, and a site containing all the information that volunteers will need. You can then submit your project for launch by clicking the "Apply for full launch" in the "Visibility" section, making it an official **Zooniverse Project** which will appear on the [main Zooniverse projects page](/projects/). A project appearing on the main Zooniverse projects list must have as its goal the production of useful research, and so there will be a further review. This final review may include requesting information from you about your plans for using the results, and looking at the results from the review phase. We may also carry out a peer review of your project.
+      During this process you should address the formal feedback provided by the volunteer project testers, as well as any comments on your project’s Talk discussion pages. Any technical issues can be reported to us via the [GitHub repository](https://github.com/zooniverse/Panoptes-Front-End), but please consider carefully any comments on workflow design.

-      At this point, you will likely want to upload more than the small subject set used for development and for the review. We’ll get in touch to arrange that once your project has passed review.
+      It is important you review the results produced at this stage to ensure they are of sufficient quality for your research. If they aren’t, please consider re-approaching your project design, make any changes, and submit for review again.
+

-      Once the project has passed review, it will appear on the [main Zooniverse project list](/projects/), and we will consider it for inclusion in a Zooniverse community newsletter or for other promotion. If you want to increase the chance that your project will be promoted, then note that we will particularly favour projects whose researchers are proactive in engaging with their community, on the Talk discussion forums and elsewhere.
+      **3. Zooniverse Project**
+      Once your project has been successfully reviewed and you have a final version of your project, you can apply for your project to be launched as an official Zooniverse project. To do this, click “Apply for full launch” in the “Visibility” section of the project builder.
+
+      Your project will then undergo a final review process. This may include requesting information from you about your plan for using the results, and examining the results from the review phase. We may also carry out a peer review of your project. This review process can take a variable amount of time, from weeks to months.
+
+      Once the project has passed review, it will appear in the [Zooniverse project list](/projects/). We will consider including your project in our Zooniverse Newsletter and other promotional material. If you want your project to be promoted, note that we favour projects whose researchers are proactive in engaging with their community, on the Talk discussion forums and elsewhere.

       _If you'd like your project to launch on a particular day, perhaps to coincide with a press release, please [get in touch](/about/contact)._
+

-      ### Rules and Regulations
-
-      By using the Zooniverse project builder, you're making use of our software and hosting, and so we reserve the right to remove content for any reason whatsoever. In particular, we will remove content and projects which are brought to our attention and where:
-
+      ### Zooniverse Policies
+      At present, we provide the Zooniverse Project Builder software and hosting for free. **We reserve the right to remove content for any reason whatsoever**. We will remove content and projects where:
       - The content is not legal.
       - The content is likely to cause offense, or is suitable only for an adult audience.
-      - The copyright on material uploaded to the site is not clear; please only use content you have the right to use!
+      - The copyright on material uploaded to the site is not clear; please only use content you have the right to use.

-      If in doubt - please [get in touch](/about/contact).
+      We reserve the right to decide which projects appear on the main project page and which are promoted to the Zooniverse community. In particular, if you have a project that is very close to an existing Zooniverse project, please contact us to discuss.

-      We're keen to find out how people want to use our platform, and at present it’s free to use. If you want more than the standard 10,000 subjects per user, are likely to want more than 100GB of space for those subjects, or will have more than a few hundred concurrent users, please let us know. We’re keen to help.
+      Projects promoted to the Zooniverse community **must**:

-      We do, however, reserve the right to have the final say on which projects appear on the main project page and which are promoted to the Zooniverse community. In particular, if you have a project that is very close to an existing Zooniverse project, please [get in touch](/about/contact) to discuss the situation; it is in no-one's interest to, for example, have seventeen different galaxy classification projects competing for attention.
+      - Have the goal of producing useful research; your study needs to be well designed, and you must intend to analyze and write up your results as a formal publication.
+      - Make their classification data open after a proprietary period, normally lasting two years from project launch.
+      - Communicate research findings to their communities, via open access publication, a blog or elsewhere.
+      - Acknowledge Zooniverse in any publications. Please use the following text:
+      "_This publication uses data generated via the [Zooniverse.org](https://www.zooniverse.org/) platform, development of which is funded by generous support, including a Global Impact Award from Google, and by a grant from the Alfred P. Sloan Foundation._"
+      - Report publications using Zooniverse-produced data to us via this [form](https://docs.google.com/forms/d/18jwLbtV_6M5HCM74xNFtFbiiszWAxpC5IGHaToYjeiw/viewform).
+

-      Projects promoted to the Zooniverse community also need to:
-
-      - Have the goal of producing useful research; this means they not only need to be well designed, but that you should have thought about who is going to use the results, and put in the effort to turn them into a formal publication.
-      - Make their classification data open after a proprietary period, normally lasting two years from project launch.
-      - Communicate results to their communities, via open access publication, a blog or elsewhere.
-      - Acknowledge Zooniverse in any publications. Please use the following text:
-
-      _This publication uses data generated via the [Zooniverse.org](https://www.zooniverse.org/) platform, development of which is funded by generous support, including a Global Impact Award from Google, and by a grant from the Alfred P. Sloan Foundation._
-
-      - Report publications using Zooniverse-produced data to us via this [form](https://docs.google.com/forms/d/18jwLbtV_6M5HCM74xNFtFbiiszWAxpC5IGHaToYjeiw/viewform).
+      If you have any questions relating to the content of this page, please [get in touch](/about/contact).
     '''

 module.exports = React.createClass


Add fake image classes

Add fake image classes

--- app/scripts/reactjs/faker/image.cjsx
+++ app/scripts/reactjs/faker/image.cjsx
@@ -4,6 +4,7 @@
   displayName: 'FakerImage'
   getDefaultProps: ->
     info: React.PropTypes.string
+    classes: React.PropTypes.string
   getInitialState: ->
     {
       href: 'http://placehold.it/100x100'
@@ -26,16 +27,20 @@
   createPlacehold: (w=100, h=100) ->
     "http://placehold.it/#{w}x#{h}"
   render: ->
-
+

 module.exports =
   FakerImage: FakerImage
   start: (elm) ->
     ImageFact = React.createFactory FakerImage
     info = elm.getAttribute 'data-faker-image'
+    classes = elm.getAttribute 'class'
+    elm.removeAttribute 'data-faker-image'
+    elm.removeAttribute 'class'
     React.render(
       ImageFact(
         info: info
+        classes: classes
       )
       elm
     )


Allow packages to provide multiple file-icon classes

--- lib/file-view.coffee
+++ lib/file-view.coffee
@@ -19,7 +19,11 @@
     @fileName.dataset.name = @file.name
     @fileName.dataset.path = @file.path

-    @fileName.classList.add(FileIcons.getService().iconClassForPath(@file.path))
+    iconClass = FileIcons.getService().iconClassForPath(@file.path)
+    if iconClass
+      unless Array.isArray iconClass
+        iconClass = iconClass.toString().split(/\s+/g)
+      @fileName.classList.add(iconClass...)

     @subscriptions.add @file.onDidStatusChange => @updateStatus()
     @updateStatus()


Don’t throw exception when @markers is undefined

--- lib/find-view.coffee
+++ lib/find-view.coffee
@@ -234,9 +234,7 @@
     @updateModel {pattern}
     @findHistory.store()

-    if @markers.length is 0
-      atom.beep()
-    else
+    if @markers?.length > 0
       selectFunction()
       if fieldToFocus
         fieldToFocus.focus()
@@ -245,6 +243,8 @@
         workspaceElement.focus()
       else
         @findEditor.focus()
+    else
+      atom.beep()

   replaceNext: =>
     @replace('findNext', 'firstMarkerIndexAfterCursor')


change default ip for tests for updated node

--- Dev/test/config.coffee
+++ Dev/test/config.coffee
@@ -1,7 +1,7 @@
 require( 'chai' ).should()
 argv = require( 'optimist' ).argv

-ip = if argv.ip? then argv.ip else "localhost"
+ip = if argv.ip? then argv.ip else "127.0.0.1"

 module.exports =
   sockets : "ws://#{ip}:8342/ws"


Uses automatic file naming convention now by default

--- javascript/render-invoice.coffee
+++ javascript/render-invoice.coffee
@@ -5,8 +5,8 @@
 # The configuration, either from the command line options, or these defaults
 model = eval fs.readFileSync(argv.json || 'javascript/test-data.js', encoding: 'utf8')
 templateFilename =       argv.template || 'templates/example-aerix/invoice.html'
-outputFilename =           argv.output || 'invoice.pdf'
-autoFilename =       argv.autoFilename
+argumentedFilename =       argv.output
+defaultFilename = 'invoice.pdf'

 phantom.create (phantomInstance) ->
   phantomInstance.createPage (page) ->
@@ -34,11 +34,16 @@
     # A callback that will do the actual export to .PDF if everything went OK
     phantomRenderPDF = (proposedFilename)->
       usefulFilename = proposedFilename? and proposedFilename.length > 0
-      if autoFilename
-        if usefulFilename
-          outputFilename = proposedFilename
-        else console.log "Template view did not return a useful filename."+
-          " Ignoring autofilename switch."
+
+      # Now we can decide which filename we shall use
+      outputFilename = defaultFilename # By defauly we go for this one
+      if usefulFilename # But if the template proposed something useful use that
+        outputFilename = proposedFilename
+      if argv.output? # But always override it if a filename was specified in the arguments
+        outputFilename = argumentedFilename
+
+      else console.log "Template view did not return a useful filename."+
+        " Ignoring autofilename switch."
       console.log "Nota server started .PDF generation job"
       page.render outputFilename, ->
         console.log "Nota server finished rendering .PDF, exiting PhantomJS thread"


Looping through each user being mentioned instead of just the first one

--- scripts/campfire-mentions.coffee
+++ scripts/campfire-mentions.coffee
@@ -31,16 +31,18 @@
     @current_user = msg.message.user.name
     @room_id = msg.message.user.room
     message = msg.message.text
-    mentioned_user = msg.match[0].slice(1)
-    return unless robot.brain.data.mentions_nicknames[mentioned_user]?

-    mail_options =
-      to: robot.brain.data.mentions_nicknames[mentioned_user]
-      from: 'no-reply'
-      subject: "#{@current_user} mentioned you in Campfire [#{process.env.HUBOT_CAMPFIRE_ACCOUNT}]"
-      html: "Hi #{mentioned_user.charAt(0).toUpperCase()}#{mentioned_user.slice(1)},
-
#{@current_user} just mentioned you in the room [#{@room_id}].

-        #{@current_user}: #{message}"
+    for user in msg.match
+      mentioned_user = user.slice(1)
+      continue unless robot.brain.data.mentions_nicknames[mentioned_user]?
+
+      mail_options =
+        to: robot.brain.data.mentions_nicknames[mentioned_user]
+        from: 'no-reply'
+        subject: "#{@current_user} mentioned you in Campfire [#{process.env.HUBOT_CAMPFIRE_ACCOUNT}]"
+        html: "Hi #{mentioned_user.charAt(0).toUpperCase()}#{mentioned_user.slice(1)},
+
#{@current_user} just mentioned you in the room [#{@room_id}].

+          #{@current_user}: #{message}"

     send_email mail_options


:ambulance:

:ambulance:

--- scripts/capital-framework.coffee
+++ scripts/capital-framework.coffee
@@ -26,13 +26,8 @@
       knew = data[0].latest.version
       if semver.gt(knew, old)
         msg = 'Version ' + knew + ' of Capital Framework was just released. ' +
-              'https://github.com/cfpb/capital-framework/blob/canary/CHANGELOG.md'
-        console.log 'Capital Framework has changed: ', knew
+              'https://github.com/cfpb/capital-framework/blob/master/CHANGELOG.md'
         robot.messageRoom process.env.HUBOT_CF_ROOM, msg
-      else
-        msg = 'Version ' + knew + ' of Capital Framework was just released. ' +
-              'https://github.com/cfpb/capital-framework/blob/canary/CHANGELOG.md'
-        console.log 'Capital Framework has not changed. ', old
       robot.brain.set 'latest-cf', knew
       return
   , 60000


:syringe:

:syringe:

--- scripts/capital-framework.coffee
+++ scripts/capital-framework.coffee
@@ -21,13 +21,15 @@
   semver = require('semver')

   pingNpm = setInterval () ->
-    npm.packages.details 'capital-framework', (err, data) ->
-      return robot.logger.error err if err
-      old = robot.brain.get('latest-cf') or '999.999.999'
-      knew = data[0].latest.version
-      if semver.gt(knew, old)
-        msg = 'Version ' + knew + ' of Capital Framework was just released. ' +
-              'https://github.com/cfpb/capital-framework/blob/master/CHANGELOG.md'
-        robot.messageRoom process.env.HUBOT_CF_ROOM, msg
-      robot.brain.set 'latest-cf', knew
-  , 60000
+    try
+      npm.packages.details 'capital-framework', (err, data) ->
+        return robot.logger.error err if err
+        old = robot.brain.get('latest-cf') or '999.999.999'
+        knew = data[0].latest.version
+        if semver.gt(knew, old)
+          msg = 'Version ' + knew + ' of Capital Framework was just released. ' +
+                'https://github.com/cfpb/capital-framework/blob/master/CHANGELOG.md'
+          robot.messageRoom process.env.HUBOT_CF_ROOM, msg
+        robot.brain.set 'latest-cf', knew
+    catch error
+  , 180000


fixing pan

fixing pan

--- static/coffee/common/plot.coffee
+++ static/coffee/common/plot.coffee
@@ -153,8 +153,8 @@

   update_range : (range_info) ->
     @pause()
-    @x_range.set(range_info.x)
-    @y_range.set(range_info.y)
+    @x_range.set(range_info.xr)
+    @y_range.set(range_info.yr)
     @unpause()

   build_tools: () ->


Small cosmetic changes

Small cosmetic changes

--- background_scripts/main.coffee
+++ background_scripts/main.coffee
@@ -388,7 +388,7 @@
     code: Settings.get("userDefinedLinkHintCss")
     runAt: "document_start"
   chrome.tabs.insertCSS tabId, cssConf, -> chrome.runtime.lastError
-  updateOpenTabs(tab) if "url" in changeInfo
+  updateOpenTabs(tab) if changeInfo.url?
   updateActiveState(tabId)

 chrome.tabs.onAttached.addListener (tabId, attachedInfo) ->
@@ -608,7 +608,7 @@
   return unless frames?

   if request.is_top # The whole tab is closing, so we can drop the frames list.
-    updateOpenTabs(sender.tab)
+    updateOpenTabs sender.tab
   else
     index = getFrameIndex frames, request.frameId
     frames.splice index, 1


added Stan's fix for try/finally without catch

--- nodes.coffee
+++ nodes.coffee
@@ -905,7 +905,7 @@
     o.top:        true
     attempt_part: @attempt.compile(o)
     error_part:   if @error then " (${ @error.compile(o) }) " else ' '
-    catch_part:   "${ (@recovery or '') and ' catch' }$error_part{\n${ @recovery.compile(o) }\n$@tab}"
+    catch_part:   if @recovery then " catch$error_part{\n${ @recovery.compile(o) }\n$@tab}" else ''
     finally_part: (@ensure or '') and ' finally {\n' + @ensure.compile(merge(o, {returns: null})) + "\n$@tab}"
     "${@tab}try {\n$attempt_part\n$@tab}$catch_part$finally_part"


using underscore's clone() for code generation

--- nodes.coffee
+++ nodes.coffee
@@ -19,15 +19,6 @@
     memo
   memo

-# Dup an array or object.
-dup: (input) ->
-  if input instanceof Array
-    val for val in input
-  else
-    output: {}
-    (output[key]: val) for key, val of input
-    output
-
 # Merge objects.
 merge: (src, dest) ->
   fresh: {}
@@ -67,7 +58,7 @@
 # the top level of a block (which would be unnecessary), and we haven't
 # already been asked to return the result.
 Node::compile: (o) ->
-  @options: dup(o or {})
+  @options: _.clone o or {}
   @indent:  o.indent
   top:      if @top_sensitive() then @options.top else del @options, 'top'
   closure:  @is_statement() and not @is_statement_only() and not top and
@@ -145,7 +136,7 @@

   # Compile each expression in the Expressions body.
   compile_node: (o) ->
-    (@compile_expression(node, dup(o)) for node in @expressions).join("\n")
+    (@compile_expression(node, _.clone(o)) for node in @expressions).join("\n")

   # If this is the top-level Expressions, wrap everything in a safety closure.
   compile_root: (o) ->
@@ -992,7 +983,7 @@
   # force sub-else bodies into statement form.
   compile_statement: (o) ->
     child:        del o, 'chain_child'
-    cond_o:       dup o
+    cond_o:       _.clone o
     del cond_o, 'returns'
     o.indent:     @idt(1)
     o.top:        true


Fixes #1980

Fixes #1980

--- nodes.coffee
+++ nodes.coffee
@@ -954,7 +954,8 @@
       @superClass = new Literal o.scope.freeVariable 'super', no
       @body.expressions.unshift new Extends lname, @superClass
       call.args.push @parent
-      call.variable.params.push new Param @superClass
+      params = call.variable.params or call.variable.base.params
+      params.push new Param @superClass

     klass = new Parens call, yes
     klass = new Assign @variable, klass if @variable


query translate fix

query translate fix

--- server/api/lib/collection.coffee
+++ server/api/lib/collection.coffee
@@ -387,6 +387,10 @@
             qry.query.filtered.filter.bool.must.push tobj
           else if typeof q[y] in ['number','boolean']
             qry.query.filtered.query.bool.must.push {query_string:{query:y + ':' + q[y]}}
+          else if typeof q[y] is 'object'
+            qobj = {}
+            qobj[y] = q[y]
+            qry.query.filtered.filter.bool.must.push q[y]
           else if q[y]?
             qry.query.filtered.filter.bool.must.push q[y]
   else if typeof q is 'string'


Add debug logs

Add debug logs

--- server/testSystems/Ejudge.coffee
+++ server/testSystems/Ejudge.coffee
@@ -25,7 +25,7 @@
     @getUser: (server, contestId, username, password, isAdmin) ->
         key = "#{server}::#{contestId}::#{username}::#{password}::#{isAdmin}"
         if not userCache[key] or (new Date() - userCache[key].loginTime > 1000 * 60 * 60)
-            logger.info "Creating new EjudgeUser ", username, contestId
+            logger.info "Creating new EjudgeUser ", username, contestId, password.length
             newUser = new LoggedEjudgeUser(server, contestId, username, password, isAdmin)
             await newUser._login()
             userCache[key] =
@@ -60,7 +60,7 @@
             timeout: 30 * 1000,
         })
         if not page.includes("Logout") and not page.includes("Выйти из системы")
-            logger.info "Can't log user #{@username} in"
+            logger.info "Can't log user #{@username} in. Page: #{page}"
             throw "Can't log user #{@username} in"
         sidString = page.match(/SID=([0-9a-f]+)/)
         logger.info "Logged user #{@username} to prog #{prog}, SID=#{sidString[1]}"


typo fixes

typo fixes

--- lib/routes.js.coffee
+++ lib/routes.js.coffee
@@ -30,7 +30,8 @@

   set_default_url_options: (optional_parts, options) ->
     for part, i in optional_parts
-      options[part] = defaults.default_url_options[part] if not options.hasOwnProperty(part) and defaults.default_url_options.hasOwnProperty(part)
+      if not options.hasOwnProperty(part) and defaults.default_url_options.hasOwnProperty(part)
+        options[part] = defaults.default_url_options[part]

   extract_anchor: (options) ->
     anchor = (if options.hasOwnProperty("anchor") then options.anchor else null)
@@ -76,15 +77,15 @@
     result += @visit(route, parameters)
     Utils.clean_path(result + anchor) + Utils.serialize(parameters)
   #
-  #     * This function is JavaScript impelementation of the
-  #     * Journey::Visitors::Formatter that builds route by given parameters
-  #     * from route binary tree.
-  #     * Binary tree is serialized in the following way:
-  #     * [node type, left node, right node ]
-  #     *
-  #     * @param  {Boolean} optional  Marks the currently visited branch as optional.
-  #     *   If set to `true`, this method will not throw when encountering
-  #     *   a missing parameter (used in recursive calls).
+  # This function is JavaScript impelementation of the
+  # Journey::Visitors::Formatter that builds route by given parameters
+  # from route binary tree.
+  # Binary tree is serialized in the following way:
+  # [node type, left node, right node ]
+  #
+  # @param  {Boolean} optional  Marks the currently visited branch as optional.
+  # If set to `true`, this method will not throw when encountering
+  # a missing parameter (used in recursive calls).
   #
   visit: (route, parameters, optional) ->
     [type, left, right] = route
@@ -110,10 +111,10 @@
         else
           throw new ParameterMissing("Route parameter missing: #{left}")
       #
-      #         * I don't know what is this node type
-      #         * Please send your PR if you do
+      # I don't know what is this node type
+      # Please send your PR if you do
       #
-      #case NodeTypes.OR:
+      # when NodeTypes.OR:
       else
         throw new Error("Unknown Rails node type")


Add some newlines

Add some newlines

--- lib-src/index.coffee
+++ lib-src/index.coffee
@@ -7,8 +7,10 @@
     if @options.includeAll
       @options.includeComments = true
       @options.includeBlankLines = true
+
   getOptions: ->
     @options
+
   parseLine: (input) ->
     if input.indexOf('\n') > -1
       throw new Error 'parseLine() will not parse multiple lines'
@@ -16,6 +18,7 @@
       [[input.trim(), []]]
     else
       []
+
   parse: (input) ->
     if input
       [[input.trim(), []]]


prune obsolete code for internal "apply" link

--- apps/partnerships/client/view.coffee
+++ apps/partnerships/client/view.coffee
@@ -23,8 +23,6 @@
     href = $(e.currentTarget).attr('href')
     e.preventDefault()
     Backbone.history.navigate $(e.currentTarget).attr('href'), trigger: true
-    if $(e.currentTarget).hasClass('partnerships-nav-apply-link')
-      mediator.once 'scrolled:position', => $('#apply-name-field').focus()

   cacheSelectors: ->
     @$nav = @$ '.partnerships-section-nav'


Fix tooltip on hover avatar

Fix tooltip on hover avatar

--- packages/rocketchat-ui-master/master/main.coffee
+++ packages/rocketchat-ui-master/master/main.coffee
@@ -248,16 +248,12 @@
 				$('.input-message').focus()
 		, 100

-	$(window).on('mouseleave', (e) ->
-		elem = $(e.fromElement)
-		avatarElem = elem.find('button.thumb')
-		if avatarElem.attr('data-username')
-			RocketChat.tooltip.hide();
+	$(document.body).on('mouseleave', 'button.thumb', (e) ->
+		RocketChat.tooltip.hide();
 	)

-	$(window).on('mouseenter', (e) ->
-		elem = $(e.fromElement)
-		avatarElem = elem.find('button.thumb')
+	$(document.body).on('mouseenter', 'button.thumb', (e) ->
+		avatarElem = $(e.currentTarget)
 		username = avatarElem.attr('data-username')
 		if username
 			e.stopPropagation()


Fix foreground color option

Fix foreground color option

--- lib/term-view.coffee
+++ lib/term-view.coffee
@@ -45,17 +45,17 @@
   if ss != null
     ssEl.parentElement.removeChild(ssEl)
   styles = "\##{termHTMLID} { color: red; }"
-  stylePrefix = "\##{termHTMLID} "
+  stylePrefix = "\##{termHTMLID}"
   styles = []
   addStyle = (s) -> styles.push(stylePrefix + s)

   i = 0
   for c in colors.slice(0, 16)
-    addStyle(".xterm-color-#{i} { color: #{c}; }")
-    addStyle(".xterm-bg-color-#{i} { background-color: #{c}; }")
+    addStyle(" .xterm-color-#{i} { color: #{c}; }")
+    addStyle(" .xterm-bg-color-#{i} { background-color: #{c}; }")
     i += 1
-  addStyle(".terminal { background: #{colors[16]}; color: #{colors[17]}}")
-  addStyle(".xterm-viewport { background: #{colors[16]}; color: #{colors[17]}}")
+  addStyle(".terminal { background: #{colors[16]}; color: #{colors[17]}; }")
+  addStyle(" .xterm-viewport { background: #{colors[16]}; color: #{colors[17]}; }")

   ssEl = document.createElement('style')
   ssEl.title = title


Don't cause errors when entry isn't found

--- shared/src/helpers/router.coffee
+++ shared/src/helpers/router.coffee
@@ -21,11 +21,11 @@
   pathToEntry: (path = window.location.pathname) =>
     findRoutePatternMemoed(path, @getRoutesMap())

-  getQuery: (options = {}) =>
+  getQuery: (options = {}) ->
     qs.parse((options.window or window).location.search.slice(1))

   currentParams: (options = {}) =>
-    @pathToEntry( (options.window or window).location.pathname)?.match.params or {}
+    @pathToEntry( (options.window or window).location.pathname)?.match?.params or {}

   makePathname: (name, params, options = {}) =>
     @getRoutesMap()[name]?.toPath(params)


Update for new errata form

Update for new errata form

Adds a few books, uses QS lib to generate url, and uses destructuring for arguments

--- shared/src/model/exercise.coffee
+++ shared/src/model/exercise.coffee
@@ -1,4 +1,4 @@
-_ = require 'underscore'
+qs = require 'qs'

 BOOK_UID_XREF =
   '27275f49-f212-4506-b3b1-a4d5e3598b99': 'Tutor Physics'
@@ -9,18 +9,19 @@
   '4f86c023-a135-412a-9d96-dcbd1ca61e7d': 'Introduction to Sociology 2e with Concept Coach'
   'd2fbadca-e4f3-4432-a074-2438c216b62a': 'Principles of Economics with Concept Coach'
   '99e127f8-f722-4907-a6b3-2d62fca135d6': 'Anatomy & Physiology with Concept Coach'
+  '3402dc53-113d-45f3-954e-8d2ad1e73659': 'Mini Biology CC Test'
+  '185cbf87-c72e-48f5-b51e-f14f21b5eabd': 'Biology'
+  '405335a3-7cff-4df2-a9ad-29062a4af261': 'Physics'

 Exercises =

-  troubleUrl: (options = {}) ->
-    url = 'https://docs.google.com/a/rice.edu/forms/d/' +
-    '1AmwbFIRvZJ6F91PQp1I6jQgqM9Kc3qNMrHdLdqhj8FE/viewform' +
-    "?entry.649352110=#{options.exerciseId}"
-    if options.project
-      url += "&entry.1655668181=#{if options.project is 'concept-coach' then 'Yes' else 'No'}"
-    if options.bookUUID and BOOK_UID_XREF[options.bookUUID]
-      url += "&entry.1091629000=#{BOOK_UID_XREF[options.bookUUID]}"
-    url
+  troubleUrl: ({bookUUID, project, exerciseId}) ->
+    # FIXME - get correct url
+    'https://oscms-dev.openstax.org/errata/form?' + qs.stringify(
+      source: project, # either tutor or CC
+      location: exerciseId,
+      book: BOOK_UID_XREF[bookUUID]
+    )

   getParts: (exercise) ->
     exercise.content?.questions or []


Use coffee script is operator

Use coffee script is operator

--- slides/dot_map/coffee/vis.coffee
+++ slides/dot_map/coffee/vis.coffee
@@ -213,14 +213,14 @@
         # console.log('no store for ' + d.store_num)
         store = d3.values(locations)[25]

-      if window.hostname == 'localhost'
+      if window.hostname is 'localhost'
         local_url = d.img_url.split("/")
         local_url = local_url[local_url.length - 1]
         local_url = "data/styles/" + local_url
-        console.log(local_url)
+        # console.log(local_url)
       else
         local_url = d.img_url
-
+
       g = {"type":"Feature", "id":d.id, "geometry":{"type":"Point", "coordinates":[store.lng,store.lat]},"properties":{'time':d.date, 'store_num':d.store_num, 'name':d.name,'img_url':local_url, 'store':store}}
       geoData.push(g)
     startTime = rawData[0].date


app: fix sharepopup

app: fix sharepopup

--- client/app/lib/sharepopup.coffee
+++ client/app/lib/sharepopup.coffee
@@ -4,8 +4,10 @@
 KDCustomHTMLView = kd.CustomHTMLView
 JView = require './jview'

+shortenUrl = require 'app/util/shortenUrl'

-class SharePopup extends JView
+
+module.exports = class SharePopup extends JView

   constructor: (options={}, data)->

@@ -70,7 +72,7 @@

     options = @getOptions()
     if options.shortenURL
-      kd.utils.shortenUrl options.url, (shorten)=>
+      shortenUrl options.url, (shorten)=>
         @urlInput.setValue shorten
         @urlInput.$().select()
         return @urlInput


updated ntp-reports to be shown in iarc-table mode

--- client/coffee/epiEvidence.coffee
+++ client/coffee/epiEvidence.coffee
@@ -69,7 +69,10 @@
                     {"type": "NtpEpiAniResults",  "fn": "animal-bioassay", "text": "Download Word: animal-bioassay"},
                 ]
             when "iarc"
-                reports = []
+                reports = [
+                    {"type": "NtpEpiDescriptive", "fn": "epi-descriptive", "text": "Download Word: study descriptions"},
+                    {"type": "NtpEpiResults",     "fn": "epi-result",      "text": "Download Word: results by organ-site"},
+                ]
             else
                 console.log("Unknown site context.")
         return reports


Add a note that any bank cards are accepted

--- client/components/Payment.coffee
+++ client/components/Payment.coffee
@@ -123,7 +123,7 @@

Получатель платежа — ИП Калинин Петр Андреевич, ОГРНИП 318527500120581, ИНН 526210494064.
             Контакты: petr@kalinin.nnov.ru, +7-910-794-32-07. (Полностью контакты указаны в разделе <Link to="/material/0">О курсе</Link>.)

-
Платежи осуществляются через Тинькофф Банк.

+
Платежи осуществляются через Тинькофф Банк. Принимаются карты любых банков.


Temporary disable payment

Temporary disable payment

--- client/components/Payment.coffee
+++ client/components/Payment.coffee
@@ -43,7 +43,7 @@
                     Форма ниже приведена для примера.
                 </Alert>
             canSubmit = false
-        else if @props.myUser.userList in ["lic40", "lic87", "zaoch", "vega", "graduated"] or @props.myUser.price == 0
+        else if @props.myUser.userList in ["lic40", "lic87", "zaoch", "vega", "graduated_"] or @props.myUser.price == 0
             amount = 0
             warning = <Alert bsStyle="danger">
                     Занятия для вас бесплатны, вам не надо их оплачивать.
@@ -56,7 +56,14 @@
                     Форма ниже приведена для примера.
                 </Alert>
             canSubmit = false
-        else
+        else if not (@props.myUser?.userList == "graduated" and @props.myUser?.price)
+            amount = 0
+            warning = <Alert bsStyle="danger">
+                    Оплата временно не работает по техническим причинам. Ожидаемое время восстановления — несколько часов.
+                    Пока можете заниматься без оплаты.
+                </Alert>
+            canSubmit = false
+        else
             amount = @props.myUser.price
             warning = null


add random rain score for client.

add random rain score for client.

--- client/scripts/coffee/app.coffee
+++ client/scripts/coffee/app.coffee
@@ -51,6 +51,16 @@
         for i in [0..4]
             @players.push(new Player(this, i))

+    generate_fake_balls: () ->
+        for i in [0..12]
+            rd = Math.random()
+            console.log rd
+            if 0.95  < rd
+                star = @balls.create i * 80, 0, 'star'
+                star.body.gravity.y = 6
+                star.body.bounce.y = 0.7
+
+
     set_players_position: () ->
         nbr_player = @players.length
         max_space_player = @phaser.width / (nbr_player + 1)
@@ -82,12 +92,6 @@
       #cursors = @phaser.input.keyboard.createCursorKeys()
       @balls = @phaser.add.group()

-      for i in [0..12]
-        star = @balls.create i * 70, 0, 'star'
-        star.body.gravity.y = 6
-        star.body.bounce.y = 0.7 #+ Math.random() * 0.2
-
-

     collectBalls: (plateform, ball) ->
       plateform.playerParent.captureBall(ball)
@@ -96,5 +100,6 @@
       @phaser.physics.overlap @plateforms, @balls, @collectBalls, null, this
       #@phaser.physics.collide @balls, @plateforms
       #@phaser.physics.overlap @balls, @collectStar, null, this
+      @generate_fake_balls()

 game = new Game()


agda-mode command activation now does not depends on package "language-agda" anymore

--- lib/agda-mode.coffee
+++ lib/agda-mode.coffee
@@ -64,7 +64,7 @@
     # register keymap bindings and emit commands
     registerCommands: ->
         @commands.forEach (command) =>
-            atom.commands.add 'atom-text-editor[data-grammar="source agda"]', command, =>
+            atom.commands.add 'atom-text-editor', command, =>
                 if isAgdaFile()
                     editor = atom.workspace.getActivePaneItem()
                     editor.core[toCamalCase command]()


set up tracking experiment

set up tracking experiment

--- desktop/apps/home/client/index.coffee
+++ desktop/apps/home/client/index.coffee
@@ -10,6 +10,7 @@
 setupHomePageModules = require './setup_home_page_modules.coffee'
 maybeShowBubble = require '../components/new_for_you/index.coffee'
 setupArtistsToFollow = require '../components/artists_to_follow/index.coffee'
+splitTest = require '../../../components/split_test/index.coffee'

 module.exports.HomeView = class HomeView extends Backbone.View
   initialize: ->
@@ -19,6 +20,8 @@

     # Render Featured Sections
     @setupHeroUnits()
+
+    splitTest('home_personalization').view()

   setupHeroUnits: ->
     new HeroUnitView


Fix test

Fix test

--- test/calculate_layout.coffee
+++ test/calculate_layout.coffee
@@ -1,11 +1,12 @@
 chai = require 'chai'
+should = chai.should()
 expect = chai.expect

 calculateLayout = require '../src/calculate_layout'

 describe 'calculateLayout', ->
   it 'should exist', ->
-    expect(calculateLayout).to.exist()
+    should.exist(calculateLayout)

   it 'should return an array with two numbers', ->
     result = calculateLayout({


deprecated AppManager

deprecated AppManager

--- client/landing/app/coffee/core/ApplicationManager.coffee
+++ client/landing/app/coffee/core/ApplicationManager.coffee
@@ -1,372 +0,0 @@
-class ApplicationManager extends KDObject
-
-  ###
-
-  * EMITTED EVENTS
-    - AppCreated                  [appController]
-    - AppIsBeingShown             [appController, appView, appOptions]
-  ###
-
-  constructor: ->
-
-    super
-
-    @appControllers = {}
-    @frontApp       = null
-    @defaultApps    =
-      text  : "Ace"
-      video : "Viewer"
-      image : "Viewer"
-      sound : "Viewer"
-
-    @on 'AppIsBeingShown', @bound "setFrontApp"
-
-    # set unload listener
-    wc = KD.singleton 'windowController'
-    wc.addUnloadListener 'window', =>
-      for own app of @appControllers when app in ['Ace', 'Terminal', 'Teamwork']
-        safeToUnload = no
-        break
-      return safeToUnload ? yes
-
-  isAppInternal : (name='')->
-    return KD.config.apps[name] and (name not in Object.keys KD.appClasses)
-
-  open: do ->
-
-    createOrShow = (appOptions = {}, appParams, callback = noop)->
-
-      name = appOptions?.name
-      return @handleAppNotFound()  unless name
-
-      appInstance = @get name
-      cb = if appParams.background or appOptions.background
-      then (appInst)-> KD.utils.defer -> callback appInst
-      else @show.bind this, name, appParams, callback
-
-      if appInstance then cb appInstance else @create name, appParams, cb
-
-    (name, options, callback)->
-
-      return warn "ApplicationManager::open called without an app name!"  unless name
-
-      [callback, options]  = [options, callback] if 'function' is typeof options
-      options            or= {}
-      appOptions           = KD.getAppOptions name
-      appParams            = options.params or {}
-      defaultCallback      = createOrShow.bind this, appOptions, appParams, callback
-
-      # If app has a preCondition then first check condition in it
-      # if it returns true then continue, otherwise call failure
-      # method of preCondition if exists
-
-      if appOptions?.preCondition? and not options.conditionPassed
-
-        if appOptions.preCondition.checkOnLoadOnly
-          if @get name then return do defaultCallback
-
-        appOptions.preCondition.condition appParams, (state, newParams)=>
-          if state
-            options.conditionPassed = yes
-            options.params = newParams  if newParams
-            @open name, options, callback
-          else
-            params = newParams or appParams
-            appOptions.preCondition.failure? params, callback
-        return
-
-      # if there is no registered appController
-      # we assume it should be a 3rd party app
-      # that's why it should be run via kodingAppsController
-
-      if not appOptions? and not options.avoidRecursion?
-
-        if @isAppInternal name
-          return KodingAppsController.loadInternalApp name, (err)=>
-            return warn err  if err
-            KD.utils.defer => @open name, options, callback
-
-        options.avoidRecursion = yes
-        return do defaultCallback
-
-      appParams = options.params or {}
-
-      if appOptions?.multiple
-        if options.forceNew or appOptions.openWith is "forceNew"
-          return @create name, appParams, (appInstance)=>
-            @showInstance appInstance, callback
-
-        switch appOptions.openWith
-          when "lastActive" then do defaultCallback
-          when "prompt"     then do defaultCallback
-
-      else do defaultCallback
-
-  openFileWithApplication: (appName, file) ->
-    @open appName, => @utils.defer => @getFrontApp().openFile file
-
-  promptOpenFileWith:(file)->
-    finderController = KD.getSingleton "finderController"
-    {treeController} = finderController
-    view = new KDView {}, file
-    treeController.showOpenWithModal view
-
-  openFile:(file)->
-
-    extension  = file.getExtension()
-    type       = FSHelper.getFileType extension
-    defaultApp = @defaultApps[extension]
-
-    return @openFileWithApplication defaultApp, file  if defaultApp
-
-    switch type
-      when 'unknown'
-        @promptOpenFileWith file
-      when 'code','text'
-        log "open with a text editor"
-        @open @defaultApps.text, (appController)-> appController.openFile file
-      when 'image'
-        log "open with an image processing app"
-      when 'video'
-        log "open with a video app"
-      when 'sound'
-        log "open with a sound app"
-      when 'archive'
-        log "extract the thing."
-
-
-  tell:(name, command, rest...)->
-
-    return warn "ApplicationManager::tell called without an app name!"  unless name
-
-    app = @get name
-    cb  = (appInstance)-> appInstance?[command]? rest...
-
-    if app then @utils.defer -> cb app
-    else @create name, cb
-
-  require: (name, params, callback) ->
-    log "AppManager: requiring an app", name
-    [callback, params] = [params, callback]  unless callback
-    callback ?= ->
-    if app = @get name
-    then callback app
-    else @create name, params, callback
-
-  create:(name, params, callback)->
-
-    [callback, params] = [params, callback]  unless callback
-
-    AppClass              = KD.getAppClass name
-    appOptions            = $.extend {}, true, KD.getAppOptions name
-    appOptions.params     = params
-    @register appInstance = new AppClass appOptions  if AppClass
-
-    if @isAppInternal name
-      return KodingAppsController.loadInternalApp name, (err)=>
-        return warn err  if err
-        KD.utils.defer => @create name, params, callback
-
-
-    @utils.defer =>
-      return @emit "AppCouldntBeCreated"  unless appInstance
-      @emit "AppCreated", appInstance
-
-      if appOptions.thirdParty
-        return KD.getSingleton("kodingAppsController").putAppResources appInstance, callback
-
-      callback? appInstance
-
-  show:(name, appParams, callback)->
-
-    appOptions  = KD.getAppOptions name
-    appInstance = @get name
-
-    appView     = appInstance.getView?()
-    return unless appView
-
-    @emit 'AppIsBeingShown', appInstance, appView, appOptions
-    appInstance.appIsShown? appParams
-
-    @setLastActiveIndex appInstance
-    @utils.defer -> callback? appInstance
-
-  showInstance:(appInstance, callback)->
-
-    appView    = appInstance.getView?() or null
-    appOptions = KD.getAppOptions appInstance.getOption "name"
-
-    return if appOptions.background
-
-    @emit 'AppIsBeingShown', appInstance, appView, appOptions
-    appInstance.appIsShown?()
-    @setLastActiveIndex appInstance
-    @utils.defer -> callback? appInstance
-
-  quit:(appInstance, callback = noop)->
-    view = appInstance.getView?()
-    destroyer = if view? then view else appInstance
-    destroyer.destroy()
-    callback()
-
-  quitAll:->
-
-    for own name, apps of @appControllers
-      @quit app  for app in apps.instances
-
-  quitByName: (name, closeAllInstances = yes) ->
-    appController = @appControllers[name]
-    return  unless appController
-
-    if closeAllInstances
-      instances = appController.instances
-      @quit instances.first while instances.length > 0
-    else
-      @quit appController.instances[appController.lastActiveIndex]
-
-  get:(name)->
-
-    if apps = @appControllers[name]
-      apps.instances[apps.lastActiveIndex] or apps.instances.first
-    else
-      null
-
-  getByView: (view)->
-
-    appInstance = null
-    for own name, apps of @appControllers
-      for appController in apps.instances
-        if view.getId() is appController.getView?()?.getId()
-          appInstance = appController
-          break
-      break if appInstance
-
-    return appInstance
-
-  getFrontApp:-> @frontApp
-
-  setFrontApp: (appInstance) ->
-
-    {router}  = KD.singletons
-    {name} = appInstance.getOptions()
-    router.setPageTitle name  if name
-    @setLastActiveIndex appInstance
-    @frontApp = appInstance
-
-  getFrontAppManifest: ->
-    {name}  = @getFrontApp().getOptions()
-    return KD.getAppOptions name
-
-  register:(appInstance)->
-
-    name = appInstance.getOption "name"
-    @appControllers[name] ?=
-      instances       : []
-      lastActiveIndex : null
-
-    @appControllers[name].instances.push appInstance
-    @setListeners appInstance
-
-    @emit "AppRegistered", name, appInstance.options
-
-  unregister:(appInstance)->
-
-    name  = appInstance.getOption "name"
-    index = @appControllers[name].instances.indexOf appInstance
-
-    if index >= 0
-      @appControllers[name].instances.splice index, 1
-
-      @emit "AppUnregistered", name, appInstance.options
-
-      if @appControllers[name].instances.length is 0
-        delete @appControllers[name]
-
-  createPromptModal:(appOptions, callback)->
-    # show modal and wait for response
-    {name} = appOptions
-    selectOptions = for instance, i in @appControllers[name].instances
-      title : "#{instance.getOption('name')} (#{i+1})"
-      value : i
-
-    modal = new KDModalViewWithForms
-      title                 : "Open with:"
-      tabs                  :
-        navigable           : no
-        forms               :
-          openWith          :
-            callback        : (formOutput)->
-              modal.destroy()
-              {index, openNew} = formOutput
-              callback index, openNew
-            fields          :
-              instance      : {
-                label       : "Instance:"
-                itemClass   : KDSelectBox
-                name        : "index"
-                type        : "select"
-                defaultValue: selectOptions.first.value
-                selectOptions
-              }
-              newOne        :
-                label       : "Open new app:"
-                itemClass   : KDOnOffSwitch
-                name        : "openNew"
-                defaultValue: no
-            buttons         :
-              Open          :
-                cssClass    : "modal-clean-green"
-                type        : "submit"
-              Cancel        :
-                cssClass    : "modal-cancel"
-                callback    : ->
-                  modal.cancel()
-                  callback null
-
-  setListeners:(appInstance)->
-
-    destroyer = if view = appInstance.getView?() then view else appInstance
-    destroyer.once "KDObjectWillBeDestroyed", =>
-      @unregister appInstance
-      appInstance.emit "AppDidQuit"
-      KD.getSingleton('appManager').emit  "AppDidQuit", appInstance
-
-  setLastActiveIndex:(appInstance)->
-
-    return unless appInstance
-
-    if optionSet = @appControllers[appInstance.getOption "name"]
-      index = optionSet.instances.indexOf appInstance
-      if index is -1 then optionSet.lastActiveIndex = null
-      else optionSet.lastActiveIndex = index
-
-
-  # setGroup:-> console.log 'setGroup', arguments
-
-  # temp
-  notification = null
-
-  notify:(msg)->
-
-    notification.destroy() if notification
-
-    notification = new KDNotificationView
-      title     : msg or "Currently disabled!"
-      type      : "mini"
-      duration  : 2500
-
-  handleAppNotFound: ->
-    new KDNotificationView
-      title    : "You don't have this app installed!"
-      type     : "mini"
-      cssClass : "error"
-      duration : 5000
-
-  # deprecate these
-
-  # fetchStorage: (appId, version, callback) ->
-  #   # warn "System still trying to access application storage for #{appId}"
-  #   KD.whoami().fetchAppStorage {appId, version}, (error, storage) =>
-  #     unless storage
-  #       storage = {appId,version,bucket:{}} # creating a fake storage
-  #     callback error, storage


add slick plugins for now

add slick plugins for now

--- config_prod.coffee
+++ config_prod.coffee
@@ -19,7 +19,10 @@
           'vendor/js/d3.v2.js'
         ]
         after: [
-          'vendor/js/d3.parcoords.js'
+          'vendor/js/d3.parcoords.js',
+          'vendor/js/slick.core.js',
+          'vendor/js/slick.grid.js',
+          'vendor/js/slick.rowselectionmodel.js'
         ]

     stylesheets:


Added properties to accepted invite event

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -8,6 +8,8 @@
 KodingError      = require '../../error'
 emailsanitize    = require './emailsanitize'
 { extend, uniq } = require 'underscore'
+
+{ protocol, hostname } = KONFIG

 module.exports = class JUser extends jraphical.Module

@@ -995,11 +997,13 @@
         group.approveMember account, roles, (err) ->
           return callback err  if err

-          Tracker.identifyAndTrack email, {
-              groupName : slug
-              email     : email
-              subject   : Tracker.types.TEAMS_ACCEPTED_INVITATION
-          }
+          groupLink  = "#{protocol}//#{invitation.groupName}.#{hostname}/"
+
+          properties =
+            groupName    : invitation.groupName
+            link         : groupLink + "Invitation/#{encodeURIComponent invitation.code}"
+
+          Tracker.identifyAndTrack email, { subject : Tracker.types.TEAMS_ACCEPTED_INVITATION }, properties

           # do not forget to redeem invitation
           redeemInvitation {


fixed nodeunit bootstrapping

fixed nodeunit bootstrapping

--- test/test-hashmonitor.coffee
+++ test/test-hashmonitor.coffee
@@ -40,6 +40,9 @@
     done()

 # Map CommonJS async test functions to the signature required by nodeunit.
-for own desc, callback of exports
-  exports[desc] = (test) ->
-    callback(test, () -> test.done())
+maptest = (description) ->
+  cjstest = exports[description]
+  exports[description] = (test) ->
+    cjstest(test, -> test.done())
+for own description of exports
+  maptest(description)


Adding test with exmaple configuration.

Adding test with exmaple configuration.

--- test/test_commandline.coffee
+++ test/test_commandline.coffee
@@ -96,5 +96,19 @@
         'works' : (error, stdout, stderr) ->
             assert.isNotNull(error)

+    'with example configuratin' :
+
+        topic : () ->
+            args = [
+                "-f"
+                "examples/coffeelint.json"
+                "test/fixtures/clean.coffee"
+            ]
+
+            commandline args, this.callback
+            return undefined
+
+        'works' : (error, stdout, stderr) ->
+            assert.isNull(error)

 }).export(module)


Support for snippets in completions

Support for snippets in completions

This is to enable suggesting e.g. `func(_foo:=<pos1>, _bar:=<pos2>)`,
where the user can tab from one pos to the next.

--- lib/autocomplete-provider.coffee
+++ lib/autocomplete-provider.coffee
@@ -50,23 +50,24 @@

                     if pascalize
                         value.displayText = value.text = PgHoffAutocompleteProvider.Pascalize(value.text)
+                        value.displayText = PgHoffAutocompleteProvider.Pascalize(value.displayText)

                     if unQuoteFunctionNames
-                        value.displayText = value.text = PgHoffAutocompleteProvider.UnQuote(value.text)
+                        value.text = PgHoffAutocompleteProvider.UnQuote(value.text)
+                        value.displayText = PgHoffAutocompleteProvider.UnQuote(value.displayText)

                     replacementPrefix = switch before[-1..-1]
                         when '*' then '*'

                     suggestion =
-                        text: value.text
-                        displayText: value.text
+                        snippet: value.text
+                        displayText: value.displayText
                         iconHTML: iconHTML
                         rightLabel: value.type
                         replacementPrefix: replacementPrefix
                         #description: 'this would be nice'
                         type: type
                     return suggestion
-
                 return suggestions

             .catch (err) ->


Fat arrow

Fat arrow

--- scripts/ayp.coffee
+++ scripts/ayp.coffee
@@ -482,9 +482,9 @@
     # Save locally, cleanup
     name = "ayp-#{@info.when}.jpg"
     outPath = path.resolve("/tmp", name)
-    @info.image.saveJpeg outPath, 95, (err) ->
+    @info.image.saveJpeg outPath, 95, (err) =>
       return cb(new Error("Failed to write result: #{err}")) if err
-      fs.readFile outPath, (err, data) ->
+      fs.readFile outPath, (err, data) =>
         return cb(new Error("Failed to read file I just wrote to #{outPath}: #{err}")) if err
         # We can unlink unconditionally now that we have it or failed
         fs.unlink(outPath, ->)


:art: refactoring

:art: refactoring

--- lib/operators.coffee
+++ lib/operators.coffee
@@ -25,12 +25,12 @@
   target: null
   complete: false
   recodable: true
-  lineWiseAlias: false
+  linewiseAlias: false

   constructor: ->
     super
     #  To support, `dd`, `cc`, `yy` `>>`, `<<`, `==`
-    if @lineWiseAlias and @vimState.isOperatorPendingMode() and
+    if @linewiseAlias and @vimState.isOperatorPendingMode() and
       @vimState.operationStack.peekTop().constructor is @constructor
         @vimState.operationStack.push new MoveToRelativeLine(@vimState)
         @abort()
@@ -57,11 +57,6 @@
     if text
       @vimState.register.set({text})

-  execute: ->
-    @editor.transact =>
-      @operate()
-    @vimState.activateNormalMode()
-
 class Select extends Operator
   @extend()
   execute: ->
@@ -69,7 +64,7 @@

 class Delete extends Operator
   @extend()
-  lineWiseAlias: true
+  linewiseAlias: true

   execute: ->
     if _.any @target.select()
@@ -77,12 +72,9 @@
       @editor.transact =>
         for selection in @editor.getSelections()
           selection.deleteSelectedText()
-      for cursor in @editor.getCursors()
-        if @target.isLinewise?()
+      if @target.isLinewise?()
+        for cursor in @editor.getCursors()
           cursor.skipLeadingWhitespace()
-        else
-          cursor.moveLeft() if cursor.isAtEndOfLine() and not cursor.isAtBeginningOfLine()
-
     @vimState.activateNormalMode()

 class DeleteRight extends Delete
@@ -112,7 +104,7 @@
       charLower

   getNewText: (text) ->
-    (@toggleCase(char) for char in text.split('')).join('')
+    text.split('').map(@toggleCase).join('')

   execute: ->
     if _.any @target.select()
@@ -138,23 +130,15 @@

 class Yank extends Operator
   @extend()
-  lineWiseAlias: true
-  execute: ->
-    originalPositions = @editor.getCursorBufferPositions()
+  linewiseAlias: true
+  execute: ->
+    if @target.isLinewise?()
+      points = @editor.getCursorBufferPositions()
     if _.any @target.select()
       @setTextToRegister @editor.getSelectedText()
-      startPositions = _.pluck(@editor.getSelectedBufferRanges(), "start")
-      # [FIXME] I can't understand this complexity.
-      # Let activateNormalMode do cursor position handling.
-      newPositions =
-        for originalPosition, i in originalPositions
-          if startPositions[i] and (@vimState.isVisualMode() or not @target.isLinewise?())
-            Point.min(startPositions[i], originalPositions[i])
-          else
-            originalPosition
-      @editor.setSelectedBufferRanges(newPositions.map (p) ->
-        new Range(p, p))
-
+      for selection in @editor.getSelections()
+        point = points?.shift() ? selection.getBufferRange().start
+        selection.cursor.setBufferPosition point
     @vimState.activateNormalMode()

 class YankLine extends Yank
@@ -229,7 +213,7 @@

 class Indent extends Operator
   @extend()
-  lineWiseAlias: true
+  linewiseAlias: true
   execute: ->
     @target.select()
     startRow = @editor.getSelectedBufferRange().start.row
@@ -437,7 +421,7 @@
 class Change extends Insert
   @extend()
   complete: false
-  lineWiseAlias: true
+  linewiseAlias: true

   execute: ->
     # If we've typed, we're being repeated. If we're being repeated,


Revert "Disable Reddit."

Revert "Disable Reddit."

This reverts commit abf2578bbdd2f0d2d4831ab7c0cc9277102c5f08.

--- config/social/jkn2-reddit.coffee
+++ config/social/jkn2-reddit.coffee
@@ -1,27 +1,27 @@

 configs = []

-# configs.push
-#   name: "Reddit Comments"
-#   comment:
-#     """
-#     We select comments, but not responses.  This avoids the need to "j" through endless lists of drivel.
-#     """
-#   regexps: "^https?://[a-z]+\\.reddit\\.com/.*/comments/"
-#   selectors: [
-#     "div#siteTable div.usertext-body"
-#     "div.commentarea > div.sitetable > div.comment"
-#   ]
-#   activators: "a[href^='http']:not(.author):not(.bylink)"
-#
-# configs.push
-#   name: "Reddit"
-#   regexps: "^https?://[a-z]+\\.reddit\\.com/?"
-#   selectors: [
-#     # "div.entry > p.title > a"
-#     "//div[@id='siteTable']/div"
-#   ]
-#   activators: "div.entry > p.title > a"
+configs.push
+  name: "Reddit Comments"
+  comment:
+    """
+    We select comments, but not responses.  This avoids the need to "j" through endless lists of drivel.
+    """
+  regexps: "^https?://[a-z]+\\.reddit\\.com/.*/comments/"
+  selectors: [
+    "div#siteTable div.usertext-body"
+    "div.commentarea > div.sitetable > div.comment"
+  ]
+  activators: "a[href^='http']:not(.author):not(.bylink)"
+
+configs.push
+  name: "Reddit"
+  regexps: "^https?://[a-z]+\\.reddit\\.com/?"
+  selectors: [
+    # "div.entry > p.title > a"
+    "//div[@id='siteTable']/div"
+  ]
+  activators: "div.entry > p.title > a"

 require("../../common.js").Common.mkConfigs configs, name: "Reddit"


Fix typo (missing e)

Fix typo (missing e)

--- server_setup.coffee
+++ server_setup.coffee
@@ -48,7 +48,7 @@
   app.use(authentication.initialize())
   app.use(authentication.session())

-setupOneSecondDelayMiddlware = (app) ->
+setupOneSecondDelayMiddleware = (app) ->
   if(config.slow_down)
     app.use((req, res, next) -> setTimeout((-> next()), 1000))

@@ -74,7 +74,7 @@
   setupMiddlewareToSendOldBrowserWarningWhenPlayersViewLevelDirectly app
   setupExpressMiddleware app
   setupPassportMiddleware app
-  setupOneSecondDelayMiddlware app
+  setupOneSecondDelayMiddleware app

 ###Routing function implementations###


Adding test to show #310 doesnt work with current implementation of before actions

--- test/spec/dispatcher_spec.coffee
+++ test/spec/dispatcher_spec.coffee
@@ -605,6 +605,34 @@
         # run synchronous and not asynchronous.
         expect(previousReturnValueToCheck).to.be 'foo'

+      it 'should handle single async. before action', ->
+        deferred = $.Deferred()
+
+        class AsyncBeforeActionChainController extends Controller
+
+          historyURL: -> 'foo'
+
+          beforeAction:
+            '.*': ->
+              # Returning a promise here triggers asynchronous behavior.
+              deferred.promise()
+
+          show: ->
+
+        controller = new AsyncBeforeActionChainController()
+
+        action = sinon.spy controller, 'show'
+
+        dispatcher.executeBeforeActionChain controller,
+          'async_before_action_chain', 'show', params, routeOptions
+
+        expect(action).was.notCalled()
+
+        # Resolve the Deferred
+        deferred.resolve()
+
+        expect(action).was.calledOnce()
+
       it 'should handle async. before actions, then pass the returned value', ->
         deferred = $.Deferred()
         promise = deferred.promise()


Test that tasks collection resets after logout

--- test/task/collection.spec.coffee
+++ test/task/collection.spec.coffee
@@ -3,6 +3,7 @@
 {TaskReview} = require 'task/review'
 Collection = require 'task/collection'
 TASK = require 'cc/tasks/C_UUID/m_uuid/GET'
+User = require 'user/model'

 describe 'Task Collection', ->

@@ -26,3 +27,9 @@
     expect(info).to.deep.equal(
       collectionUUID:"C_UUID", moduleUUID:"m_uuid", link:'test/contents/C_UUID:m_uuid'
     )
+
+
+  it 'is reset after user logs out', ->
+    expect( Collection.getModuleInfo(@taskId, 'test') ).not.to.be.undefined
+    User._signalLogoutCompleted()
+    expect( Collection.getModuleInfo(@taskId, 'test') ).to.be.undefined


Added more informative toolbar descriptions

--- lib/spark-ide.coffee
+++ lib/spark-ide.coffee
@@ -91,13 +91,16 @@
         .then (pkg) =>
           @toolbar = pkg.mainModule
           @toolbar.appendSpacer()
-          @flashButton = @toolbar.appendButton 'flash', 'spark-ide:flash-cloud', 'Flash using cloud', 'ion'
-          @compileButton = @toolbar.appendButton 'checkmark-circled', 'spark-ide:compile-cloud', 'Compile in the cloud', 'ion'
+          @flashButton = @toolbar.appendButton 'flash', 'spark-ide:flash-cloud', 'Compile and upload code using cloud', 'ion'
+          @compileButton = @toolbar.appendButton 'checkmark-circled', 'spark-ide:compile-cloud', 'Compile and show errors if any', 'ion'

           @toolbar.appendSpacer()

-          @coreButton = @toolbar.appendButton 'pinpoint', 'spark-ide:select-core', 'Select Core...', 'ion'
-          @wifiButton = @toolbar.appendButton 'wifi', 'spark-ide:setup-wifi', 'Setup Core\'s WiFi...', 'ion'
+          @toolbar.appendButton 'document-text', ->
+            require('shell').openExternal('http://docs.spark.io/')
+          , 'Opens reference at docs.spark.io', 'ion'
+          @coreButton = @toolbar.appendButton 'pinpoint', 'spark-ide:select-core', 'Select which Core you want to work on', 'ion'
+          @wifiButton = @toolbar.appendButton 'wifi', 'spark-ide:setup-wifi', 'Setup Core\'s WiFi credentials', 'ion'
           @toolbar.appendButton 'usb', 'spark-ide:show-serial-monitor', 'Show serial monitor', 'ion'

           @updateToolbarButtons()


App: log current user browser and basic info

--- client/app/lib/index.coffee
+++ client/app/lib/index.coffee
@@ -32,6 +32,8 @@
   # that depend on `remote`, and here we are. -og
   remote = require('./remote')
   # it is very important that you invoke this method before anything else does, so f important.
+
+  console.log 'Application loaded with browser info:', require 'bowser'

   globals.os = os # linux, mac or windows
   globals.keymapType = do ->


KDFramework: transitionend event binding minor improvement

--- core/KDView.coffee
+++ core/KDView.coffee
@@ -515,7 +515,7 @@
       'webkitTransition': 'webkitTransitionEnd'

     transitionEvent = 'transitionend'
-    for key, val of transitions when el.style[key]?
+    for key, val of transitions when key of el.style
       transitionEvent = val
       break


Unfocus on escape

Unfocus on escape

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -38,7 +38,7 @@
     @command 'tree-view:move', => @moveSelectedEntry()
     @command 'tree-view:add', => @add()
     @command 'tree-view:remove', => @removeSelectedEntry()
-    @command 'tool-panel:unfocus', => @detach()
+    @command 'tool-panel:unfocus', => @unfocus()

     @on 'tree-view:directory-modified', =>
       if @hasFocus()


Add defensive check

Add defensive check

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -1240,6 +1240,9 @@
         else if change.type is 'ContentChange'

           {content, path} = context.file
+
+          return  unless content
+
           string = @rtm.getFromModel path
           string.setText content  if string


AppController: When trim whitespace settings is updated, update flag in each editor view's ace.

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -892,6 +892,8 @@
       if view instanceof Class
         if component is 'editor'
           view.getAce()[method]? value
+          if key is 'trimTrailingWhitespaces'
+            view.getAce().isTrimWhiteSpaceEnabled = value
         else
           view.webtermView.updateSettings()


Handle symlinks

Handle symlinks

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -742,13 +742,16 @@
       if selectedEntry and initialPath and fs.existsSync(initialPath)
         basePath = selectedEntry.getPath()
         basePath = path.dirname(basePath) if selectedEntry.classList.contains('file')
-        basePath += path.sep # Helps out with the recursive copying check below
         newPath = path.join(basePath, path.basename(initialPath))

         # Do not allow copying test/a/ into test/a/b/
-        if initialPathIsDirectory and basePath.startsWith(initialPath + path.sep)
-          atom.notifications.addWarning('Cannot paste a folder into itself')
-          continue
+        # Note: A trailing path.sep is added to prevent false positives, such as test/a -> test/ab
+        realBasePath = fs.realpathSync(basePath) + path.sep
+        realInitialPath = fs.realpathSync(initialPath) + path.sep
+        if initialPathIsDirectory and realBasePath.startsWith(realInitialPath)
+          unless fs.isSymbolicLinkSync(initialPath)
+            atom.notifications.addWarning('Cannot paste a folder into itself')
+            continue

         if copiedPaths
           # append a number to the file if an item with the same name exists
@@ -866,10 +869,12 @@
     if initialPath is newDirectoryPath
       return

-    console.log newDirectoryPath
-    if fs.isDirectorySync(initialPath) and newDirectoryPath.startsWith(initialPath + path.sep)
-      atom.notifications.addWarning('Cannot move a folder into itself')
-      return
+    realNewDirectoryPath = fs.realpathSync(newDirectoryPath) + path.sep
+    realInitialPath = fs.realpathSync(initialPath) + path.sep
+    if fs.isDirectorySync(initialPath) and realNewDirectoryPath.startsWith(realInitialPath)
+      unless fs.isSymbolicLinkSync(initialPath)
+        atom.notifications.addWarning('Cannot move a folder into itself')
+        return

     entryName = path.basename(initialPath)
     newPath = path.join(newDirectoryPath, entryName).replace(/\s+$/, '')


added age and babies charts

added age and babies charts

--- demo.coffee
+++ demo.coffee
@@ -35,9 +35,10 @@
     ar2.push ',' if i && !( i % 3 )
     ar2.push v
   ar2.reverse().join ''
-create = (tag, cz) ->
+create = (tag, cz, id) ->
   e = document.createElement(tag)
-  e.setAttribute( 'class', cz ) if cz
+  e.setAttribute( 'class', cz ) if cz?
+  e.id = id if id?
   e
 chartType = 'options'
 [ u, makeCharts, loaded ] = [ null, false, false ]
@@ -100,6 +101,10 @@
     sp.innerHTML = s.value
     values[0] = Number.parseInt s.value
 madeGeneCharts = false
+chartDiv = ( tab, id ) ->
+  div = create 'div', 'chart', id
+  tab.appendChild div
+  id
 makeUniverse = ->
   p = convertParams()
   p.callback = collectData
@@ -107,12 +112,25 @@
   unless madeGeneCharts
     for type in [ 'plant', 'herbivore', 'carnivore' ]
       tab = byId "#{type}-chart"
+      charts = evoData.charts[type] = {}
+      id = chartDiv tab, "#{type}-babies"
+      charts.babies =
+        id:        id
+        type:      'interval'
+        htitle:    'Time (ticks)'
+        vtitle:    'Offspring per individual'
+        collector: statCollector titleize(type), (d) -> d.babies
+        rows:      []
+      id = chartDiv tab, "#{type}-age"
+      charts.age =
+        id:        id
+        type:      'interval'
+        htitle:    'Time (ticks)'
+        vtitle:    'Age (ticks)'
+        collector: statCollector titleize(type), (d) -> u.tick - d.tick
+        rows:      []
       for gene, v of u.urThing(type).genes
-        id = "#{type}-#{gene}"
-        div = create 'div', 'chart'
-        div.id = id
-        tab.appendChild div
-        charts = evoData.charts[type] ||= {}
+        id = chartDiv tab, "#{type}-#{gene}"
         charts[gene] = geneChartSpec type, gene, id
 evoData =
   generation: 0


Remove typing status after timeout;

Remove typing status after timeout;

--- client/lib/msgTyping.coffee
+++ client/lib/msgTyping.coffee
@@ -13,14 +13,13 @@
 			stream.on room, (typing) ->
 				unless typing.username is Meteor.user()?.username
 					if typing.start
-						console.log 'start', typing
 						users = usersTyping[room].users.get()
 						users[typing.username] = Meteor.setTimeout ->
 							delete users[typing.username]
+							usersTyping[room].users.set users
 						, timeout
 						usersTyping[room].users.set users
 					else if typing.stop
-						console.log 'stop', typing
 						users = usersTyping[room].users.get()
 						delete users[typing.username]
 						usersTyping[room].users.set users


Removing tab indentation.

Removing tab indentation.

--- test/numbers.coffee
+++ test/numbers.coffee
@@ -50,9 +50,9 @@
 eq Number::toString, (4).toString

 test '#1168: leading floating point suppresses newline', ->
-	eq 1, do ->
-		1
-		.5 + 0.5
+  eq 1, do ->
+    1
+    .5 + 0.5

 test "Python-style octal literal notation '0o777'", ->
   eq 511, 0o777


Handle the success callback

Handle the success callback

--- lib/vie-redis.coffee
+++ lib/vie-redis.coffee
@@ -34,9 +34,9 @@
             reference = model.fromReference reference
             console.log "Adding reference #{predicate}-#{reference} for #{model.getSubjectUri()}"
             redisClient.sadd "#{predicate}-#{reference}", model.getSubjectUri()
-        redisClient.hset model.getSubjectUri(), predicate, JSON.stringify(object), (err) ->
-          return options.error err if err
-          options.success model
+        console.log "hset", model.getSubjectUri(), predicate
+        redisClient.hset model.getSubjectUri(), predicate, JSON.stringify(object), ->
+      options.success model

     if method is 'read'
       if model instanceof Backbone.Collection


:lipstick: Extract ::pushOperations from ::handleCommands helper

--- lib/vim-state.coffee
+++ lib/vim-state.coffee
@@ -152,24 +152,7 @@
   handleCommands: (commands) ->
     _.each commands, (fn, commandName) =>
       eventName = "vim-mode:#{commandName}"
-
-      @editorView.command eventName, (e) =>
-        possibleOperations = fn(e)
-        possibleOperations = if _.isArray(possibleOperations) then possibleOperations else [possibleOperations]
-        for possibleOperation in possibleOperations
-          # Motions in visual mode perform their selections.
-          if @mode == 'visual' and possibleOperation instanceof motions.Motion
-            possibleOperation.origExecute = possibleOperation.execute
-            possibleOperation.execute = possibleOperation.select
-
-          @opStack.push(possibleOperation) if possibleOperation
-
-          # If we've received an operator in visual mode, mark the current
-          # selection as the motion to operate on.
-          if @mode == 'visual' and possibleOperation instanceof operators.Operator
-            @opStack.push(new motions.CurrentSelection(@))
-
-          @processOpStack()
+      @editorView.command eventName, (event) => @pushOperations(fn(event))

   # Private: Attempts to prevent the cursor from selecting the newline
   # while in command mode.
@@ -180,6 +163,25 @@
   moveCursorBeforeNewline: =>
     if not @editor.getSelection().modifyingSelection and @editor.cursor.isOnEOL() and @editor.getCurrentBufferLine().length > 0
       @editor.setCursorBufferColumn(@editor.getCurrentBufferLine().length - 1)
+
+  # Private: Push the given operations onto the operation stack, then process
+  # it.
+  pushOperations: (operations) ->
+    operations = [operations] unless _.isArray(operations)
+
+    for operation in operations
+      # Motions in visual mode perform their selections.
+      if @mode is 'visual' and operation instanceof motions.Motion
+        operation.execute = operation.select
+
+      @opStack.push(operation) if operation
+
+      # If we've received an operator in visual mode, mark the current
+      # selection as the motion to operate on.
+      if @mode is 'visual' and operation instanceof operators.Operator
+        @opStack.push(new motions.CurrentSelection(@))
+
+      @processOpStack()

   # Private: Removes all operations from the stack.
   #


Expect Error instead of JSON string in markdown preview view

--- lib/markdown-preview-view.coffee
+++ lib/markdown-preview-view.coffee
@@ -52,8 +52,8 @@
   getPath: ->
     @buffer.getPath()

-  setErrorHtml: (result)->
-    try failureMessage = JSON.parse(result).message
+  setErrorHtml: (result) ->
+    failureMessage = result?.message

     @html $$$ ->
       @h2 'Previewing Markdown Failed'


:lipstick:

:lipstick:

--- lib/markdown-preview-view.coffee
+++ lib/markdown-preview-view.coffee
@@ -91,9 +91,8 @@

   renderMarkdown: ->
     @setLoading()
-    roaster(@buffer.getText(), {}, (err, html) =>
+    roaster @buffer.getText(), {}, (err, html) =>
       if err
         @setErrorHtml(err)
       else
         @html(@tokenizeCodeBlocks(html))
-    )