Fixed selected text formatting test Fixed unsupported language notification test

--- spec/format-spec.coffee
+++ spec/format-spec.coffee
@@ -29,7 +29,7 @@
         expect(atom.workspace.getActiveTextEditor().getText()).not.toMatch(@fileText)

     it "can format a selection of the whole buffer with the use of the command", ->
-      # general format test
+      # general selected text format test

       waitsForPromise ->
         atom.workspace.open('specfiles/index.js')
@@ -37,15 +37,15 @@
       runs ->
         # select text until end of first require
         atom.workspace.getActiveTextEditor().setSelectedBufferRange([[0, 0], [14, 7]])
-        fileText = atom.workspace.getActiveTextEditor().getSelectedText()
-        restOfFileText = atom.workspace.getActiveTextEditor().getTextInBufferRange([[15, 0], [31, 0]])
+        @fileText = atom.workspace.getActiveTextEditor().getSelectedText()
+        @restOfFileText = atom.workspace.getActiveTextEditor().getTextInBufferRange([[15, 0], [31, 0]])
         atom.workspaceView.getActiveView().trigger 'jsformat:format'

       runs ->
         # check that some whitespace and other goodies got added
-        expect(atom.workspace.getActiveTextEditor().getSelectedText()).not.toMatch(fileText)
+        expect(atom.workspace.getActiveTextEditor().getSelectedText()).not.toMatch(@fileText)
         # check that the rest of the file wasn't touched
-        expect(atom.workspace.getActiveTextEditor().getTextInBufferRange([[15, 0], [31, 0]])).not.toMatch(restOfFileText)
+        expect(atom.workspace.getActiveTextEditor().getTextInBufferRange([[15, 0], [31, 0]])).not.toMatch(@restOfFileText)

     it "can format the whole buffer if Format on save is turned on", ->
@@ -66,15 +66,15 @@
     it "displays a notification for unsupported languages", ->
       # NotSupportedNotificationView test

-      displayUnsupportedLanguageNotification = jasmine.createSpy('format.displayUnsupportedLanguageNotification')
+      waitsForPromise ->
+        atom.packages.activatePackage('jsformat')

       waitsForPromise ->
+        spyOn(format, 'displayUnsupportedLanguageNotification').andCallThrough()
         atom.workspace.open('xyz.coffee')

       runs ->
         atom.workspaceView.getActiveView().trigger('jsformat:format')
-        displayUnsupportedLanguageNotification()

-        # TODO seems a little stupid to expect something I'm calling, but I can't figure it out, so this is a placeholder
-
-        expect(displayUnsupportedLanguageNotification.calls.length).toEqual(1)
+        expect(format.displayUnsupportedLanguageNotification).toHaveBeenCalled()
+        expect(format.displayUnsupportedLanguageNotification.callCount).toEqual(1)


Fix interval cancellation in specs

Fix interval cancellation in specs

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -261,7 +261,9 @@
 window.resetTimeouts = ->
   window.now = 0
   window.timeoutCount = 0
+  window.intervalCount = 0
   window.timeouts = []
+  window.intervalTimeouts = {}

 window.fakeSetTimeout = (callback, ms) ->
   id = ++window.timeoutCount
@@ -272,13 +274,15 @@
   window.timeouts = window.timeouts.filter ([id]) -> id != idToClear

 window.fakeSetInterval = (callback, ms) ->
+  id = ++window.intervalCount
   action = ->
     callback()
-    window.fakeSetTimeout(action, ms)
-  window.fakeSetTimeout(action, ms)
+    window.intervalTimeouts[id] = window.fakeSetTimeout(action, ms)
+  window.intervalTimeouts[id] = window.fakeSetTimeout(action, ms)
+  id

 window.fakeClearInterval = (idToClear) ->
-  window.fakeClearTimeout(idToClear)
+  window.fakeClearTimeout(@intervalTimeouts[idToClear])

 window.advanceClock = (delta=1) ->
   window.now += delta


Update pt-BR.coffee

Update pt-BR.coffee

--- app/locale/pt-BR.coffee
+++ app/locale/pt-BR.coffee
@@ -845,19 +845,19 @@
     playtime: "Tempo de Jogo"
     last_played: "Último Jogo"
     leagues_explanation: "Jogar em um campeonato contra outros membros do clã nestes casos de arena multiplayer."
-#    track_concepts1: "Track concepts"
-#    track_concepts2a: "learned by each student"
-#    track_concepts2b: "learned by each member"
-#    track_concepts3a: "Track levels completed for each student"
-#    track_concepts3b: "Track levels completed for each member"
-#    track_concepts4a: "See your students'"
-#    track_concepts4b: "See your members'"
-#    track_concepts5: "solutions"
-#    track_concepts6a: "Sort students by name or progress"
-#    track_concepts6b: "Sort members by name or progress"
-#    track_concepts7: "Requires invitation"
-#    track_concepts8: "to join"
-#    private_require_sub: "Private clans require a subscription to create or join."
+    track_concepts1: "Rastrear conceitos"
+    track_concepts2a: "aprendido por cada estudante"
+    track_concepts2b: "aprendido por cada membro"
+    track_concepts3a: "Rastrear níveis completados por cada estudante"
+    track_concepts3b: "Track levels completed for each memberRastrear níveis completados por cada membro"
+    track_concepts4a: "Ver seus alunos'"
+    track_concepts4b: "Ver seus membros'"
+    track_concepts5: "soluções"
+    track_concepts6a: "Classificar alunos por nome ou progresso"
+    track_concepts6b: "Classificar membros por nome ou progresso"
+    track_concepts7: "Requer convite"
+    track_concepts8: "para se juntar"
+    private_require_sub: "Clãs particulares requerem uma assinatura para criar ou juntar-se."

 #  courses:
 #    course: "Course"


Provide fallback card image

Provide fallback card image

--- app/pages/projects.cjsx
+++ app/pages/projects.cjsx
@@ -30,6 +30,7 @@
   imagePromise: (project) ->
     project.get('avatar')
       .then (avatar) -> avatar.src
+      .catch -> '/assets/simple-avatar.jpg'

   cardLink: (project) ->
     link = if !!project.redirect


fix pw reset indention

fix pw reset indention

--- apps/auth/client.coffee
+++ apps/auth/client.coffee
@@ -49,4 +49,4 @@

       new ResetPasswordView
         el: $('#reset-password-page')
-      model: pwReset
+        model: pwReset


Add range info to result tree

Add range info to result tree

--- assets/js/tracer.coffee
+++ assets/js/tracer.coffee
@@ -52,8 +52,10 @@
 editor.getSession().setMode 'ace/mode/javascript'
 editor.setShowPrintMargin false
 editor.getSession().on 'change', (event) ->
-	try
-		display treeify(parse editor.getValue()).toString()
+	#try
+		display treeify(parse editor.getValue()).toObject()
+	#catch err
+	#	console.error err

 output = ace.edit 'output'
 output.setTheme 'ace/theme/monokai'
@@ -65,18 +67,21 @@

 parse = (code) ->
 	try
-		esprima.parse code
+		esprima.parse code,
+			range: true
 	catch err
 		{ 'Parse error': err }
+		{}

 display = (data) ->
-	try
-		data = JSON.stringify(data, null, 2) unless typeof data == 'string'
-	catch err
-		data = { 'JSON error': err }
-	output.setValue(data)
+	# try
+	# 	data = JSON.stringify(data, null, 2) unless typeof data == 'string'
+	# catch err
+	# 	data = { 'JSON error': err }
+	output.setValue(JSON.stringify(data, null, 2))
 	output.clearSelection()
+

 Syntax =
@@ -132,24 +137,46 @@
 	#'YieldExpression': ['argument'] # Harmony

-treeify = (block, scope, identifiers) ->
-	scope = new Tree(null, []) unless scope?
-
+class Scope
+
+	constructor: (@range) ->
+		@range ?= [0, 0]
+		@variables = []
+		console.log "new Scope([#{@range[0]}, #{@range[1]}])"
+
+	add: (variable) ->
+		throw 'Variable must be an instance of Variable' unless variable instanceof Variable
+		@variables.push variable
+
+	has: (id) ->
+		(variable.name for variable in @variables when variable.name == id).length > 0
+
+
+class Variable
+
+	constructor: (@name, @range) ->
+		console.log "new Variable('#{@name}', [#{@range[0]}, #{@range[1]}])"
+
+
+
+treeify = (block, scope, declaration) ->
+	# In case block is optional in parent block
 	if not block then return scope

+	# In case block is an array of blocks
 	if block instanceof Array
-		treeify(el, scope, identifiers) for el in block
+		treeify(el, scope, declaration) for el in block
 		return scope

 	switch block.type
 		when 'Identifier'
-			if identifiers
-				if -1 == scope.root().indexOf block.name
-					scope.root().push block.name
+			if declaration
+				if not scope.root().has block.name
+					scope.root().add new Variable block.name, block.range
 				else
-					#console.log 'Variable %s already defined', block.name
+					console.log 'Variable %s already defined', block.name
 		when 'FunctionDeclaration', 'FunctionExpression'
-			local = new Tree(scope, [])
+			local = new Tree scope, new Scope block.range
 			treeify block.id, scope
 			treeify block.params, local, true
 			treeify block.defaults, local
@@ -159,6 +186,10 @@
 		when 'ObjectExpression'
 			for prop in block.properties
 				treeify prop.value, scope
+		when 'Program'
+			# Program creates a new scope
+			scope = new Tree null, new Scope
+			treeify block.body, scope
 		when 'VariableDeclarator'
 			treeify block.id, scope, true
 			treeify block.init, scope
@@ -167,6 +198,6 @@
 				for prop in Syntax[block.type]
 					treeify block[prop], scope
 			else
-				console.log 'No handler for type #{block.type}'
+				#console.log 'No handler for type %s of %o', block.type, block

 	scope


Fix message order problem

Fix message order problem

--- assets/javascripts/models.js.coffee
+++ assets/javascripts/models.js.coffee
@@ -14,7 +14,7 @@
   roomUserState: DS.belongsTo("room_user_state")
   messages: DS.hasMany("message")
   users: DS.hasMany("user")
-
+  # historyAvailable: DS.attr("virtual", {defaultValue: true})

 App.RoomUserState = DS.Model.extend
@@ -77,10 +77,10 @@
     @afterMessageId = message.id

     if(@store.recordIsLoaded("user", message.user.id))
-      @store.find("user", message.user.id).then (user)=>
+      successCallback = (user)=>
         messageObj.set("user", user)
-        #TODO push or shift depending on the query
-        @room.get("messages").pushObject(messageObj)
+
+      @store.find("user", message.user.id).then successCallback
     else
       userParams =
         id: message.user.id
@@ -89,8 +89,8 @@
         role: message.user.role
       user = @store.createRecord("user", userParams)
       messageObj.set("user", user)
-      #TODO push or shift depending on the query
-      @room.get("messages").pushObject(messageObj)
+    #TODO push or shift depending on the query
+    @room.get("messages").pushObject(messageObj)

   fetchMessages: ->


added arrows

added arrows

--- maze.coffee
+++ maze.coffee
@@ -97,21 +97,40 @@
           height: @height
       })
       context = floor[0].getContext('2d')
-      context.lineWidth = 1
-      context.strokeStyle = 'black'
       for y in [-1...@maze.dimensions[1]]
         for x in [-1...@maze.dimensions[0]]
           if !@maze.passage_exists([[x, y, z], [x + 1, y, z]])
             context.beginPath()
             context.moveTo(71.5 + 71 * x, 0.5 + 71 * y)
             context.lineTo(71.5 + 71 * x, 71.5 + 71 * y)
+            context.lineWidth = 1
+            context.strokeStyle = 'black'
             context.stroke()
           if !@maze.passage_exists([[x, y, z], [x, y + 1, z]])
             context.beginPath()
             context.moveTo(0.5 + 71 * x, 71.5 + 71 * y)
             context.lineTo(71.5 + 71 * x, 71.5 + 71 * y)
-            context.stroke()
-          # TODO paint arrows
+            context.lineWidth = 1
+            context.strokeStyle = 'black'
+            context.stroke()
+          if @maze.passage_exists([[x, y, z], [x, y, z + 1]])
+            # paint up arrow
+            context.beginPath()
+            context.moveTo(x * 71 + 27, y * 71 + 13)
+            context.lineTo(x * 71 + 35, y * 71 + 8)
+            context.lineTo(x * 71 + 43, y * 71 + 13)
+            context.lineWidth = 3
+            context.strokeStyle = 'grey'
+            context.stroke()
+          if @maze.passage_exists([[x, y, z - 1], [x, y, z]])
+            # paint down arrow
+            context.beginPath()
+            context.moveTo(x * 71 + 27, y * 71 + 57)
+            context.lineTo(x * 71 + 35, y * 71 + 62)
+            context.lineTo(x * 71 + 43, y * 71 + 57)
+            context.lineWidth = 3
+            context.strokeStyle = 'grey'
+            context.stroke()
       @floors[z] = floor
       @div.append(floor)
       floor.addClass('floor')


Start substring after trailing slash

Start substring after trailing slash

--- src/app/git.coffee
+++ src/app/git.coffee
@@ -48,7 +48,7 @@
   relativize: (path) ->
     workingDirectory = @getWorkingDirectory()
     if workingDirectory and path.indexOf("#{workingDirectory}/") is 0
-      path.substring(workingDirectory.length)
+      path.substring(workingDirectory.length + 1)
     else
       path


Documentation

Documentation

--- src/bounded.coffee
+++ src/bounded.coffee
@@ -197,14 +197,14 @@
   the center of the object.

-    player = Core
-      x: 50
-      y: 40
-
-    player.include(Bounded)
-
-    player.position()
-    => {x: 50, y: 40}
+  player = Core
+    x: 50
+    y: 40
+
+  player.include(Bounded)
+
+  player.position()
+  => {x: 50, y: 40}

   @name center


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/bounded.coffee
+++ src/bounded.coffee
@@ -11,7 +11,8 @@
   I ||= {}

   ###*
-  The bind method adds a function as an event listener.
+  The bounds method returns infomation about
+  the location of the object and its dimensions

   @name bounds
   @methodOf Bounded#


eliminate render

eliminate render

--- src/context.coffee
+++ src/context.coffee
@@ -24,7 +24,7 @@

   handle_renderable = (object) ->
     if object?._lead_render
-      @render object
+      @add_renderable object
       true

   handle_using_extension = (object) ->
@@ -171,10 +171,6 @@
         run_context.add_rendered $item
         $item

-      render: (o) ->
-        run_context.current_context.nested 'renderable', run_context.add_renderable, o
-        # TODO warn if not renderable
-
       renderable: (o, fn) ->
         o._lead_render = fn
         o


[Bugfix] Mobile image scroller

[Bugfix] Mobile image scroller

--- src/mobile/components/artwork_columns/view.coffee
+++ src/mobile/components/artwork_columns/view.coffee
@@ -4,10 +4,6 @@
 artworkItem = -> require('./artwork.jade') arguments...

 module.exports = class ArtworkColumns extends Backbone.View
-
-  initialize: ->
-    unless @$('.artwork-columns-column').length > 0
-      throw "You must render columns before using this view."

   # Build up columns of artworks, then append to each existing column
   renderArtworks: (artworkColumns) ->


very simplistic attach method

very simplistic attach method

--- src/decking.coffee
+++ src/decking.coffee
@@ -61,6 +61,7 @@
         start   start a cluster of containers
         stop    stop a cluster
         status  check the status of a cluster's containers
+        attach  attach to all running containers in a cluster
       """

       log help
@@ -84,6 +85,7 @@
     start: (done) -> @_run "start", done
     stop: (done) -> @_run "stop", done
     status: (done) -> @_run "status", done
+    attach: (done) -> @_run "attach", done

   _run: (cmd, done) ->
     [cluster] = @args
@@ -123,6 +125,31 @@
         else
           logAction name, "skipping (already stopped)"
           callback null
+
+    resolveOrder @config, cluster, (list) ->
+
+      validateContainerPresence list, (err) ->
+        return done err if err
+
+        async.eachSeries list, iterator, done
+
+  attach: (cluster, done) ->
+
+    options =
+      stream: true
+      stdout: true
+      stderr: true
+      tty: false
+
+    iterator = (details, callback) ->
+      name = details.name
+      container = docker.getContainer name
+      container.attach options, (err, stream) ->
+        # woah! the stream data is a bit complex. ideally i want to pipe
+        # each container's logs to a different writable stream, not process.stdout
+        container.modem.demuxStream stream, process.stdout, process.stderr
+
+        callback null

     resolveOrder @config, cluster, (list) ->


apparently, I'm just having a total brain fart wrt arrays and objects

--- src/scripts/githooks/coreos_restart/status.coffee
+++ src/scripts/githooks/coreos_restart/status.coffee
@@ -18,7 +18,7 @@

   # Parse the output for structured data.
   data = stdout.split("\n")[1..]
-  status = {}
+  status = []
   for line in data
     continue if line == ""

@@ -27,7 +27,7 @@

     # Save service status, ignoring those not part of this deployment.
     if name in keys services
-      status[name] = fields[2]
+      status.push "#{name}": fields[2]
     else
       continue


removed bad require in test causing redis to be required in unit test #shouldhavebeenabountyhunter

--- services/web/test/UnitTests/coffee/Subscription/SubscriptionHandlerTests.coffee
+++ services/web/test/UnitTests/coffee/Subscription/SubscriptionHandlerTests.coffee
@@ -3,7 +3,6 @@
 sinon = require 'sinon'
 querystring = require 'querystring'
 modulePath = "../../../../app/js/Features/Subscription/SubscriptionHandler"
-SubscriptionHandler = require modulePath

 mockRecurlySubscriptions =
 	"subscription-123-active":


Make authentication work with mongoose

Make authentication work with mongoose

--- server/initializers/authentication.coffee
+++ server/initializers/authentication.coffee
@@ -5,20 +5,26 @@
   done(null, user.id)

 passport.deserializeUser (id, done) ->
-  User = require('../models/user')
-  User.find(id).
-    success( (user) ->
+  User = require('../models/user').model
+  User
+    .find(id)
+    .exec( (err, user) ->
+      if err?
+        console.error err
+        return done(err)
+
       done(null, user)
-    ).
-    failure( (err) ->
-      done(err)
     )

 passport.use(
   new BasicStrategy(
     (username, password, done) ->
-      User = require('../models/user')
-      User.find(where: {email: username}).success((user) ->
+      User = require('../models/user').model
+      User.find({email: username}, (err, user) ->
+        if err?
+          console.error err
+          return done(err)
+
         if !user
           return done(null, false, { message: 'Incorrect username.' })

@@ -26,8 +32,6 @@
           return done(null, false, { message: 'Incorrect password.' })

         return done(null, user)
-      ).failure((error) ->
-        return done(null, false)
       )
   )
 )


Fix indent

Fix indent

--- src/plugins/image_insert_edit.coffee
+++ src/plugins/image_insert_edit.coffee
@@ -104,8 +104,7 @@
       # Prevent contextual toolbar from showing when image is clicked.
       @options.editable.element.on 'halloselected', (event, data) ->
         toolbar_option = widget.options.editable.options.toolbar
-        if toolbar_option == "halloToolbarContextual" and
-         jQuery(data.originalEvent.target).is('img')
+        if toolbar_option == "halloToolbarContextual" and jQuery(data.originalEvent.target).is('img')
           $toolbar.hide()
           false

@@ -161,8 +160,7 @@
         jQuery(document).scrollTop(scrollbar_pos)  # restore scrollbar pos
         @options.editable.keepActivated false

-      if @options.insert_file_dialog_ui_url and not
-       @dialog_image_selection_ui_loaded
+      if @options.insert_file_dialog_ui_url and not @dialog_image_selection_ui_loaded

         @options.dialog.on 'click', ".reload_link", ->
           widget._load_dialog_image_selection_ui()


Adds more expression components to expression_to_string

--- javascripts/lib/math/expression_to_string.coffee
+++ javascripts/lib/math/expression_to_string.coffee
@@ -2,14 +2,14 @@
   ['lib/class_mixer', 'lib/object_refinement', 'lib/math/expression_components'],
   (class_mixer, object_refinement, comps)->

-
     ref = object_refinement.build()
-
     ref.forDefault({
       toString: ->
-        '?'
+        #console.log "toString not handled for #{AP @unrefined()}"
+        "?"
       toHTMLString: ->
-        '?'
+        #console.log "toHTMLString not handled for #{AP @unrefined()}"
+        "?"
       })

     ref.forType(comps.addition, {
@@ -23,11 +23,24 @@
       power: ->
         ref.refine(@unrefined().power()).toString()
       toString: ->
-        "#{@base()}^#{@power()}"
+        "#{@base()} ^ #{@power()}"
       toHTMLString: ->
-        "#{@base()}ˆ#{@power()}"
+        "#{@base()} ˆ #{@power()}"
       });

+    ref.forType(comps.multiplication, {
+      toString: ->
+        "*"
+      toHTMLString: ->
+        "×"
+      });
+
+    ref.forType(comps.subtraction, {
+      toString: ->
+        "*"
+      toHTMLString: ->
+        "×"
+      })

     ref.forType(comps.expression, {
       toString: ->
@@ -37,6 +50,11 @@
       mapconcatWithMethod: (method)->
         _(@expression).map((it)-> ref.refine(it)[method]()).join(' ')
       });
+
+    ref.forType(comps.blank, {
+      toString: -> ""
+      toHTMLString: -> ""
+      })

     ref.forType(comps.number, {
       toString: ->


add missing letter

add missing letter

--- server/imap/connection.coffee
+++ server/imap/connection.coffee
@@ -24,7 +24,7 @@
 # SENT FROM : some php script (probably very specific)
 # @TODO : PR to mailparser to be more lenient in parsing headers ?
 _old1 = MailParser::_parseHeaderLineWithParams
-ailParser::_parseHeaderLineWithParams = (value) ->
+MailParser::_parseHeaderLineWithParams = (value) ->
     _old1.call this, value.replace '" format=flowed', '"; format=flowed'


Update settings.coffee

Update settings.coffee

--- server/models/settings.coffee
+++ server/models/settings.coffee
@@ -13,7 +13,7 @@
     displayPreview       : type : Boolean, default : true
     layoutStyle          : type : String,  default : 'column'
     listStyle            : type : String,  default : 'default'
-    messageConfirmDelete : type : Boolean, default : true
+    messageConfirmDelete : type : Boolean, default : false
     messageDisplayHTML   : type : Boolean, default : true
     messageDisplayImages : type : Boolean, default : false
     plugins              : type : Any,     default : null
@@ -40,7 +40,7 @@
         displayPreview       : true
         layoutStyle          : 'column'
         listStyle            : 'default'
-        messageConfirmDelete : true
+        messageConfirmDelete : false
         messageDisplayHTML   : true
         messageDisplayImages : false
         plugins:


improve performance for migrating large dochistory collections

--- migrations/3_pack_docHistory_collection.coffee
+++ migrations/3_pack_docHistory_collection.coffee
@@ -113,10 +113,20 @@
 			console.log "completed #{count}/#{totalDocCount} processed=#{processedFraction.toFixed(2)} remaining=#{remainingFraction.toFixed(2)} elapsed=#{(t-t0)/1000} est Finish=#{estFinishTime}"
 		interval = setInterval printProgress, 3*1000

-		jobs = _.map _.filter(ids, (id) -> not finished_docs[id]), (id)->
-			return (cb)->
-				processNext(id, cb)
-		async.series jobs, (err)->
+		nextId = null
+
+		testFn = () ->
+			return false if needToExit
+			id = ids.shift()
+			while id? and finished_docs[id]	# skip finished
+				id = ids.shift()
+			nextId = id
+			return nextId?
+
+		executeFn = (cb) ->
+			processNext nextId, cb
+
+		async.whilst testFn, executeFn, (err)->
 			if err?
 				console.error err, "at end of jobs"
 			else
@@ -126,6 +136,10 @@

 exports.rollback = (client, done)->
 	done()
+
+# process.nextTick () ->
+# 	exports.migrate () ->
+# 		console.log "done"

 DAYS = 24 * 3600 * 1000 # one day in milliseconds


add fields

add fields

--- src/server/models/player_game.coffee
+++ src/server/models/player_game.coffee
@@ -13,6 +13,12 @@
         type: Sequelize.INTEGER
         allowNull: false
         defaultValue: 0
+    isFirstKilledNight:
+        type: Sequelize.BOOLEAN
+        defaultValue: false
+    isFirstKilledDay:
+        type: Sequelize.BOOLEAN
+        defaultValue: false
 }, {
 })


invitation from CoCalc, not SMC

invitation from CoCalc, not SMC

--- src/smc-webapp/course/actions.coffee
+++ src/smc-webapp/course/actions.coffee
@@ -520,7 +520,7 @@
             if '@' in x
                 if not do_not_invite_student_by_email
                     title   = s.getIn(['settings', 'title'])
-                    subject = "SageMathCloud Invitation to Course #{title}"
+                    subject = "CoCalc Invitation to Course #{title}"
                     body    = body.replace(/{title}/g, title).replace(/{name}/g, name)
                     body    = markdownlib.markdown_to_html(body).s
                     @redux.getActions('projects').invite_collaborators_by_email(student_project_id, x, body, subject, true, replyto, name)


MemberRemoved: notify requester to update it's machine list

--- workers/social/lib/social/models/computeproviders/handlers/memberremoved.coffee
+++ workers/social/lib/social/models/computeproviders/handlers/memberremoved.coffee
@@ -17,7 +17,10 @@

   return owner

-setOwnerOfStack = (stack, newOwnerId, oldOwner) ->
+
+setOwnerOfStack = (stack, newOwner, oldOwner, group) ->
+
+  newOwnerId = newOwner.getId()

   stack.unuseStackTemplate (err) ->
     log 'Failed to mark as unused stack:', err  if err
@@ -28,14 +31,16 @@
         'config.oldOwner' : oldOwner
       }
     }, (err) ->
-      log 'Failed to change ownership of stack:', err  if err
+      if err
+      then log 'Failed to change ownership of stack:', err
+      else newOwner.sendNotification 'MachineListUpdated', { group }

-updateStacks = ({ reason, stacks, oldOwner, requesterId }) ->
+updateStacks = ({ reason, stacks, oldOwner, requester, group }) ->

   if reason is 'kick'
     stacks.forEach (stack) ->
-      setOwnerOfStack stack, requesterId, oldOwner
+      setOwnerOfStack stack, requester, oldOwner, group
   else
     stacks.forEach (stack) ->
       stack.delete (err) ->
@@ -46,7 +51,7 @@

   # Update machine ownership to the admin who kicked the member
   machine.addUsers {
-    targets: [ user ], asOwner: yes, sudo: yes, group: group.slug
+    targets: [ user ], asOwner: yes, sudo: yes, group: group.slug, inform: no
   }, (err) ->
     log 'Failed to change ownership of machine:', err  if err

@@ -178,7 +183,8 @@
       updateStacks {
         stacks   : memberStacks
         oldOwner : memberJUser.getAt 'username'
-        requesterId, reason
+        group    : group.slug
+        requester, reason
       }
       next()


Default operationId

Default operationId

--- src/swagged-angular-resources.coffee
+++ src/swagged-angular-resources.coffee
@@ -52,7 +52,7 @@

         memo[modelDefinition].push({
           path: path.replace(/\{(.+)\}/g, ":$1")
-          nickname: operation.operationId
+          nickname: operation.operationId || modelDefinition + "_" + _.str.capitalize(action)
           action: action.toUpperCase()
           summary: operation.summary


Activity: sidebar items unread counts improved

--- client/Social/Activity/sidebar/activitysidebar.coffee
+++ client/Social/Activity/sidebar/activitysidebar.coffee
@@ -43,20 +43,40 @@
       .on 'MessageListUpdated',        (update) -> log update.event, update
       .on 'ReplyRemoved',              (update) -> log update.event, update

+      .on 'ParticipantUpdated',        @bound 'handleGlanced'
       # .on 'ChannelUpdateHappened',     @bound 'channelUpdateHappened'

   # event handling

   messageAddedToChannel: (update) ->
-    if update.channel.typeConstant is 'pinnedactivity'
-      @replyAdded update
-
-  messageRemovedFromChannel: (update)->
+    switch update.channel.typeConstant
+      when 'pinnedactivity' then @replyAdded update
+      when 'topic'          then @handleFollowedFeedUpdate update
+
+  messageRemovedFromChannel: (update) ->
     log 'messageRemovedFromChannel', update
     {id} = update.channelMessage

     @removeItem id
+
+  handleGlanced: (update) ->
+
+    @selectedItem?.setUnreadCount update.unreadCount
+
+
+  handleFollowedFeedUpdate: (update) ->
+
+    {socialapi}   = KD.singletons
+    {unreadCount} = update
+    {id}          = update.channel
+
+    socialapi.cacheable 'channel', id, (err, data) =>
+
+      return KD.showError err  if err
+
+      item = @addItem data, yes
+      item.setUnreadCount unreadCount

   replyAdded: (update) ->
@@ -94,15 +114,16 @@

   accountAddedToChannel: (update) ->

-    {socialapi} = KD.singletons
-    {id}        = update.channel
+    {socialapi}   = KD.singletons
+    {unreadCount} = update
+    {id}          = update.channel

     socialapi.cacheable 'channel', id, (err, channel) =>

       return KD.showError err  if err

-      @addItem channel, yes
-      # @updateTopicFollowButtons id, yes  if channel.typeConstant is 'topic'
+      item = @addItem channel, yes
+      item.setUnreadCount unreadCount or 1

   accountRemovedFromChannel: (update) ->
@@ -110,18 +131,11 @@
     {id, typeConstant} = update.channel

     @removeItem id
-    # @updateTopicFollowButtons id, no  if channel.typeConstant is 'topic'

   channelUpdateHappened: (update) ->

-    # log 'ChannelUpdateHappened', update
-
     log 'dont use this, educational purposes only!', update
-
-    # switch update.event
-    #   when 'MessageAddedToChannel'     then return @addToChannel update.channelMessage
-    #   when 'MessageRemovedFromChannel' then return @removeFromChannel update.channelMessage

     # @setChannelUnreadCount update  if update.channel


Sidebar: fixed a minor typo

Sidebar: fixed a minor typo

--- client/Social/Activity/sidebar/activitysidebar.coffee
+++ client/Social/Activity/sidebar/activitysidebar.coffee
@@ -303,7 +303,7 @@

   getItemByRouteParams: (type, slug) ->

-    typeConstant = k for own k, v of typeMap when v = type
+    typeConstant = k for own k, v of typeMap when v is type

     itemWeWant = null
     for own name, section of @sections


allow tabs to be used in sidebar

allow tabs to be used in sidebar

--- app/assets/javascripts/active_admin/application.js.coffee
+++ app/assets/javascripts/active_admin/application.js.coffee
@@ -24,8 +24,8 @@
   $('.filter_form_field.select_and_search select').change ->
     $(@).siblings('input').prop name: "q[#{@value}]"

-  # Tab navigation in the show page
-  $('#main_content .tabs').tabs()
+  # Tab navigation
+  $('#active_admin_content .tabs').tabs()

   # In order for index scopes to overflow properly onto the next line, we have
   # to manually set its width based on the width of the batch action button.


copy helper functions from CollaboratorsHandler

--- services/web/app/coffee/Features/Collaborators/CollaboratorsInviteHandler.coffee
+++ services/web/app/coffee/Features/Collaborators/CollaboratorsInviteHandler.coffee
@@ -1,5 +1,85 @@
+UserCreator = require('../User/UserCreator')
+Project = require("../../models/Project").Project
+mimelib = require("mimelib")
+logger = require('logger-sharelatex')
+UserGetter = require "../User/UserGetter"
+ContactManager = require "../Contacts/ContactManager"
+CollaboratorsEmailHandler = require "./CollaboratorsEmailHandler"
+Async = require "async"
+PrivilegeLevels = require "../Authorization/PrivilegeLevels"
+Errors = require "../Errors/Errors"
+
 module.experts = CollaboratorsInviteHandler =

+	# helpers
+	getMemberIdsWithPrivilegeLevels: (project_id, callback = (error, members) ->) ->
+		Project.findOne { _id: project_id }, { owner_ref: 1, collaberator_refs: 1, readOnly_refs: 1 }, (error, project) ->
+			return callback(error) if error?
+			return callback new Errors.NotFoundError("no project found with id #{project_id}") if !project?
+			members = []
+			members.push { id: project.owner_ref.toString(), privilegeLevel: PrivilegeLevels.OWNER }
+			for member_id in project.readOnly_refs or []
+				members.push { id: member_id.toString(), privilegeLevel: PrivilegeLevels.READ_ONLY }
+			for member_id in project.collaberator_refs or []
+				members.push { id: member_id.toString(), privilegeLevel: PrivilegeLevels.READ_AND_WRITE }
+			return callback null, members
+
+	getMemberIds: (project_id, callback = (error, member_ids) ->) ->
+		CollaboratorsInviteHandler.getMemberIdsWithPrivilegeLevels project_id, (error, members) ->
+			return callback(error) if error?
+			return callback null, members.map (m) -> m.id
+
+	getMembersWithPrivilegeLevels: (project_id, callback = (error, members) ->) ->
+		CollaboratorsInviteHandler.getMemberIdsWithPrivilegeLevels project_id, (error, members = []) ->
+			return callback(error) if error?
+			result = []
+			async.mapLimit members, 3,
+				(member, cb) ->
+					UserGetter.getUser member.id, (error, user) ->
+						return cb(error) if error?
+						if user?
+							result.push { user: user, privilegeLevel: member.privilegeLevel }
+						cb()
+				(error) ->
+					return callback(error) if error?
+					callback null, result
+
+	getMemberIdPrivilegeLevel: (user_id, project_id, callback = (error, privilegeLevel) ->) ->
+		# In future if the schema changes and getting all member ids is more expensive (multiple documents)
+		# then optimise this.
+		CollaboratorsInviteHandler.getMemberIdsWithPrivilegeLevels project_id, (error, members = []) ->
+			return callback(error) if error?
+			for member in members
+				if member.id == user_id?.toString()
+					return callback null, member.privilegeLevel
+			return callback null, PrivilegeLevels.NONE
+
+	getMemberCount: (project_id, callback = (error, count) ->) ->
+		CollaboratorsInviteHandler.getMemberIdsWithPrivilegeLevels project_id, (error, members) ->
+			return callback(error) if error?
+			return callback null, (members or []).length
+
+	getCollaboratorCount: (project_id, callback = (error, count) ->) ->
+		CollaboratorsInviteHandler.getMemberCount project_id, (error, count) ->
+			return callback(error) if error?
+			return callback null, count - 1 # Don't count project owner
+
+	isUserMemberOfProject: (user_id, project_id, callback = (error, isMember, privilegeLevel) ->) ->
+		CollaboratorsInviteHandler.getMemberIdsWithPrivilegeLevels project_id, (error, members = []) ->
+			return callback(error) if error?
+			for member in members
+				if member.id.toString() == user_id.toString()
+					return callback null, true, member.privilegeLevel
+			return callback null, false, null
+
+	getProjectsUserIsCollaboratorOf: (user_id, fields, callback = (error, readAndWriteProjects, readOnlyProjects) ->) ->
+		Project.find {collaberator_refs:user_id}, fields, (err, readAndWriteProjects)=>
+			return callback(err) if err?
+			Project.find {readOnly_refs:user_id}, fields, (err, readOnlyProjects)=>
+				return callback(err) if err?
+				callback(null, readAndWriteProjects, readOnlyProjects)
+
+	# public functions
 	inviteToProject: (projectId, sendingUserId, email, priveleges, callback=(err,invite)->) ->

 	revokeInvite: (projectId, inviteId, callback=(err)->) ->


Fixed validation on 'Date started trading' question

--- app/assets/javascripts/frontend/form-validation.js.coffee
+++ app/assets/javascripts/frontend/form-validation.js.coffee
@@ -136,7 +136,7 @@
       @addErrorMessage(question, "Emails don't match")

   validateMaxDate: (question) ->
-    val = question.find("input[type='text']").val()
+    val = question.find("input[type='number']").val()

     questionYear = parseInt(question.find(".js-date-input-year").val())
     questionMonth = parseInt(question.find(".js-date-input-month").val())


Be sure shape can return its measure.

Be sure shape can return its measure.

--- app/assets/javascripts/leaflet/reactive_measure.js.coffee
+++ app/assets/javascripts/leaflet/reactive_measure.js.coffee
@@ -1,3 +1,19 @@
+# Extends featureGroup to return measure. Works with inherited classes (L.MultiPolygon, L.MultiPolyline)
+L.FeatureGroup.include
+  getMeasure: () ->
+
+    measure =
+      perimeter: 0
+      area: 0
+
+    this.eachLayer (layer) ->
+      m = layer.getMeasure()
+      measure.perimeter += m.perimeter
+      measure.area += m.area
+
+    measure
+
+
 L.Polygon.include
   ###
   # Get centroid of the polygon in square meters
@@ -354,9 +370,10 @@

     if layers.getLayers().length > 0
       layers.eachLayer (layer) =>
-        m = layer.getMeasure()
-        @options.measure.perimeter += m.perimeter
-        @options.measure.area += m.area
+        if typeof layer.getMeasure() is 'function'
+          m = layer.getMeasure()
+          @options.measure.perimeter += m.perimeter
+          @options.measure.area += m.area

   onAdd: (map) ->
     @_container = L.DomUtil.create('div', 'reactive-measure-control')


Clear `isGuest` flag when user logs in or registers an account

--- app/assets/javascripts/sprangular/services/account.coffee
+++ app/assets/javascripts/sprangular/services/account.coffee
@@ -37,6 +37,7 @@
       Cart.load(@user.current_order) if @user.current_order
       @isLogged = true
       @email = data.email
+      @isGuest = false

     populateGuestAccount: (data) ->
       @isGuest = true


Use createSpinner on journal index.

Use createSpinner on journal index.

--- app/assets/javascripts/views/journal_index_view.js.coffee
+++ app/assets/javascripts/views/journal_index_view.js.coffee
@@ -1,9 +1,4 @@
-ETahi.JournalIndexView = Ember.View.extend
+ETahi.JournalIndexView = Ember.View.extend ETahi.SpinnerMixin,
   toggleSpinner: (->
-    if @get('controller.epubCoverUploading')
-      @spinnerDiv = @$('#epub-cover-spinner')[0]
-      @spinner ||= new Spinner(color: "#aaa").spin(@spinnerDiv)
-      $(@spinnerDiv).show()
-    else
-      $(@spinnerDiv).hide()
-  ).observes('controller.epubCoverUploading')
+    @createSpinner('controller.epubCoverUploading', '#epub-cover-spinner', color: '#aaa')
+  ).observes('controller.epubCoverUploading').on('didInsertElement')


Fixed a missing if statement

Fixed a missing if statement

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

   configFile: './config.json'
   options:
-    publicDir: './testfiles'
-    jsPath: './js/mylibs'
-    cssPath: './css/style.less'
-    outputJs: './js/script.js'
-    outputCss: './css/style.css'
+    public: './public'
+    jsPath: './app'
+    cssPath: './styles/style.less'
+    outputJs: './public/js/scripts.js'
+    outputCss: '.public/css/styles.css'

   constructor: ->
     @readConfig()
@@ -30,7 +30,7 @@

   # Actions
   new: ->
-    console.log (!fpath.existsSync @configFile), argv.force
+    if true and (!fpath.existsSync(@configFile) or argv.force)
       util.log 'Creating config file'
       fs.writeFileSync @configFile, JSON.stringify(@options, null, 2)
     else


Reinstate problem_check event as Problem Checked event

--- common/lib/xmodule/xmodule/js/src/capa/display.coffee
+++ common/lib/xmodule/xmodule/js/src/capa/display.coffee
@@ -162,8 +162,9 @@
   #       maybe preferable to consolidate all dispatches to use FormData
   ###
   check_fd: =>
-    # Calling check from check_fd will result in firing the 'problem_check' event twice, since it is also called in the check function.
-    #Logger.log 'problem_check', @answers
+    # Calling check from check_fd will result in firing the 'problem_check' event twice,
+    # since it is also called in the check function.
+    # Logger.log 'problem_check', @answers

     # If there are no file inputs in the problem, we can fall back on @check
     if $('input:file').length == 0
@@ -239,6 +240,11 @@
   check: =>
     @check_waitfor()
     Logger.log 'problem_check', @answers
+
+    # Segment.io
+    analytics.track "Problem Checked",
+      answers: @answers
+
     $.postWithPrefix "#{@url}/problem_check", @answers, (response) =>
       switch response.success
         when 'incorrect', 'correct'


CONFIG: changed path of the Notes App

CONFIG: changed path of the Notes App

--- config-prod.coffee
+++ config-prod.coffee
@@ -40,7 +40,7 @@
         stylesheets:
             joinTo:
                 '../build/CNeditor.css': /^app\/CNeditor/
-                '../../cozy-notes/client/vendor/styles/CNeditor.css': /^app\/CNeditor/
+                '../../notes/client/vendor/styles/CNeditor.css': /^app\/CNeditor/
             order:
                 before:[
                     'app/CNeditor/datepicker.css'
@@ -48,4 +48,4 @@

     modules:
-        wrapper: 'commonjs'+        wrapper: 'commonjs'


improved new stack trace API

improved new stack trace API

--- src/lib/log.coffee
+++ src/lib/log.coffee
@@ -103,7 +103,7 @@
 class log.StackTrace

   # Create a new instance of `StackTrace` for the `caller`.
-  constructor: (caller) ->
+  constructor: (caller = log.StackTrace) ->
     # Create the stack trace and assign it to a new `stack` property.
     Error.captureStackTrace this, caller


Only load datepickers if the JS is available.

This prevents JavaScript errors on our login page, where the datepicker
javascript is (and shouldn't be) available.

--- src/SumoCoders/FrameworkCoreBundle/Resources/assets/coffee/framework.form.coffee
+++ src/SumoCoders/FrameworkCoreBundle/Resources/assets/coffee/framework.form.coffee
@@ -86,6 +86,8 @@
     )

   _dateFields: ->
+    return if (!$.isFunction($.fn.datepicker))
+
     $.datepicker.setDefaults @_dateFieldOptions

     $('[data-date-type] input[type="text"]', @form).each((i, el) =>


prioritize conditions in fundings component

--- app/assets/javascripts/components/company_profile/content/funding/view.js.coffee
+++ app/assets/javascripts/components/company_profile/content/funding/view.js.coffee
@@ -9,10 +9,12 @@
     company: @props.company

   render: ->
-    if _.isEmpty(@state.company.fundings)
-      @_renderEmpty()
+    if @state.edit_mode
+      @_renderListings()
     else if @state.funding
       CompanyProfile_Content_Funding_Form(funding: @state.funding, saveProfile: @props.saveProfile, onCancel: @_onFormCancel)
+    else if _.isEmpty(@state.company.fundings)
+      @_renderEmpty()
     else
       @_renderListings()


Changed iframe resize function

Changed iframe resize function

Instead of resizing the iframe based on the height of the body element
of the iframe content, do it based on the entire content height. Also,
don't compare the inner content height to the body height of the outer
page, but use the iframe height instead.

--- app/assets/javascripts/locomotive/views/inline_editor/application_view.js.coffee
+++ app/assets/javascripts/locomotive/views/inline_editor/application_view.js.coffee
@@ -30,11 +30,11 @@

       if @_$('meta[name=inline-editor]').size() > 0
         # bind the resize event. When the iFrame's size changes, update its height
-        iframe_content = iframe.contents().find('body')
+        iframe_content = iframe.contents()
         iframe_content.resize ->
           elem = $(this)

-          if elem.outerHeight(true) > $('body').outerHeight(true) # Resize the iFrame.
+          if elem.outerHeight(true) > iframe.outerHeight(true) # Resize the iFrame.
             iframe.css height: elem.outerHeight(true)

         # Resize the iFrame immediately.


brightness / contrast working in rgb, fixes #660

--- app/assets/javascripts/oxalis/geometries/materials/plane_material_factory.coffee
+++ app/assets/javascripts/oxalis/geometries/materials/plane_material_factory.coffee
@@ -130,6 +130,7 @@

         <% if (isRgb) { %>
           vec3 data_color = texture2D( <%= layers[0] %>_texture, vUv * repeat + offset).xyz;
+          data_color = (data_color + brightness - 0.5) * contrast + 0.5;
         <% } else { %>
           vec3 data_color = vec3(0.0, 0.0, 0.0);


No clippy. Flash might not work on mobile.

--- app/assets/javascripts/angular-directives.coffee
+++ app/assets/javascripts/angular-directives.coffee
@@ -134,28 +134,6 @@
 pwgDirectives.directive 'dropdownMenuItem', [dropdownMenuItem]

 # -----------------------------------------------------------------------------
-# The GitHub Clippy widget
-# -----------------------------------------------------------------------------
-
-clippy = ->
-  restrict:    'EA'
-  transclude:  false
-  replace:     true
-  templateUrl: templateURL('directives/clippy.html')
-  scope:
-    text:    "@"
-    bgColor: "@"
-
-  link: (scope, element, attrs) ->
-    unless attrs.ngHide?
-      scope.ngHide = false
-    unless attrs.ngShow?
-      scope.ngShow = true
-    return
-
-pwgDirectives.directive 'clippy', [clippy]
-
-# -----------------------------------------------------------------------------
 # Fake checkbox, useful when you want to support clicking outside the checkbox
 # itself
 # -----------------------------------------------------------------------------


Prefill building division for animal_housing_mulching, animal_housing_cleaning.

--- app/assets/javascripts/backend/animals.js.coffee
+++ app/assets/javascripts/backend/animals.js.coffee
@@ -55,8 +55,18 @@
         options = Array.from(arguments).shift()
         options['reference_name'] ||= 'animal'
         options['targets_attributes'] ||= []
-        options['targets_attributes'] = ko.utils.arrayMap Object.keys(@selectedItemsIndex), (id) ->
-          { product_id: id, reference_name: options['reference_name'], new_container_id: options['container'], new_group_id: options['group'] }
+
+        building_division_procedures = ['animal_housing']
+
+        unless building_division_procedures.indexOf(options['reference_name']) == -1
+          # set container instead of animals
+          for id, item of @selectedItemsIndex
+            if item.parent isnt undefined
+              options['targets_attributes'].push { product_id: item.parent.id, reference_name: options['reference_name'] }
+
+        else
+          options['targets_attributes'] = ko.utils.arrayMap Object.keys(@selectedItemsIndex), (id) ->
+            { product_id: id, reference_name: options['reference_name'], new_container_id: options['container'], new_group_id: options['group'] }

         parameters = options['parameters'] || false
         base_url = options['base_url'] || $('a[data-target=animal_group_changing]').attr('href')


removed todos panel, cause notes should cover it.

--- app/assets/javascripts/mercury/mercury.js.coffee
+++ app/assets/javascripts/mercury/mercury.js.coffee
@@ -148,7 +148,6 @@
         historypanel:          ['History', 'Page Version History', {panel: '/mercury/panels/history'}]
         sep3:                  ' '
         notespanel:            ['Notes', 'Page Notes', {panel: '/mercury/panels/notes'}]
-        todospanel:            ['Todos', 'Page Todos', {panel: '/mercury/panels/todos'}]

       editable:
         _regions:              ['editable']


Add jpeg as an image extension

Add jpeg as an image extension

--- app/assets/javascripts/mtl/document_modal.coffee
+++ app/assets/javascripts/mtl/document_modal.coffee
@@ -1,4 +1,4 @@
-IMAGE_EXTENSIONS = ['png', 'jpg', 'gif', 'bmp']
+IMAGE_EXTENSIONS = ['png', 'jpg', 'gif', 'bmp', 'jpeg']
 MTL.templates.document_modal = """


fix multiple positions shown (but not on refresh); fix does not redirect to results page

Former-commit-id: eb69b7a9751487e63a2fb4dd41726e214a85691b [formerly 6bf5724dba3faeae0f16e173b9c4d8094189476d] [formerly 09dfd3eec1599941c03823e098fcbd80279504eb [formerly 09dfd3eec1599941c03823e098fcbd80279504eb [formerly 95f60e744cf3c95c8819acce79142e2cb41a8dd5]]]
Former-commit-id: 5459367531135a1145f1bbd4327d00482d05e481 [formerly e326b56752f8e77fb4609dd258e824c4fc6d14a1]
Former-commit-id: aeb57b5ffbe153d0d7f5fc8773909f5b9336a463
Former-commit-id: f2f363f86cdb4a00cc5c384803e9df68dba4b90b

--- app/assets/javascripts/views/positionview.coffee
+++ app/assets/javascripts/views/positionview.coffee
@@ -20,10 +20,10 @@
       @listenTo ConsiderIt.app, 'user:signout', @post_signout

   post_signin : () ->
-    return if !@model.positions
+    return if !@proposal.positions

     point.set('user_id', ConsiderIt.current_user.id) for point in @model.written_points
-    existing_position = @model.positions[ConsiderIt.current_user.id]
+    existing_position = @proposal.positions[ConsiderIt.current_user.id]

     # need to merge old position into new
     existing_position.subsume @model


[#85679260] place Downloads JS namespace with Attachments

--- app/assets/javascripts/yoolk/namespace.js.coffee
+++ app/assets/javascripts/yoolk/namespace.js.coffee
@@ -20,7 +20,7 @@
 window.Yoolk.Views.Home                 ||= {}
 window.Yoolk.Views.Videos               ||= {}
 window.Yoolk.Views.Links                ||= {}
-window.Yoolk.Views.Downloads            ||= {}
+window.Yoolk.Views.Attachments          ||= {}

 # widgets
 window.Yoolk.Widgets                    ||= {}


Use console.warn for Alchemy.debug

Use console.warn for Alchemy.debug

We use Alchemy.debug in our JS to warn users about things that fail.

Closes #1452

--- app/assets/javascripts/alchemy/alchemy.base.js.coffee
+++ app/assets/javascripts/alchemy/alchemy.base.js.coffee
@@ -103,8 +103,7 @@
   # Logs exception to js console, if present.
   debug: (e) ->
     if window["console"]
-      console.debug e
-      console.trace()
+      console.warn(e)
     return

   # Logs errors to js console, if present.


begin implementing rectangle focus

begin implementing rectangle focus

--- app/assets/javascripts/components/subject-viewer.cjsx
+++ app/assets/javascripts/components/subject-viewer.cjsx
@@ -283,7 +283,8 @@
                 </g>
             }

-            { if @state.subject.location.spec.tool is 2
+            { # ROW FOCUS TOOL -------------------------------------------
+              if @props.workflow.name is "transcribe" and @state.subject.location.spec.tool is 2
                 markHeight = @state.subject.location.spec.yLower - @state.subject.location.spec.yUpper
                 <g>

@@ -307,6 +308,32 @@
                 </g>
             }

+
+            { # RECTANGLE FOCUS TOOL ------------------------------------------
+              if @props.workflow.name is "transcribe" and @state.subject.location.spec.tool is 1
+                markHeight = @state.subject.location.spec.yLower - @state.subject.location.spec.yUpper
+                <g>
+
+                  <rect
+                    className   = "mark-rectangle"
+                    x           = 0
+                    y           = { @state.subject.location.spec.y }
+                    width       = { @state.imageWidth }
+                    height      = { @state.subject.location.spec.y }
+                    fill        = "rgba(0,0,0,0.6)"
+                  />
+
+                  <rect
+                    className   = "mark-rectangle"
+                    x           = 0
+                    y           = { @state.subject.location.spec.yLower }
+                    width       = { @state.imageWidth }
+                    height      = { @state.imageHeight - @state.subject.location.spec.yLower }
+                    fill        = "rgba(0,0,0,0.6)"
+                  />
+                </g>
+            }
+
           </svg>

     <div className="subject-viewer#{if @props.active then ' active' else ''}">


Fix opacity of unused series in dynamic demand chart

When a serie is made invisible, it is no longer included in the
list of visibleData. This can cause D3 to reuse a g.serie element
which was previously assigned to another serie. The initial chart
may be:

    1. data.key = serie_one
    2. data.key = serie_two
    3. data.key = serie_three

If serie_one is made invisible, D3 will use that SVG element for
serie_two, and the element for serie_two is used for serie_three.
Since elements are hidden based on their keys, this results in the
old serie_three element still being visible:

    1. data.key = serie_two
    2. data.key = serie_three
    3. data.key = serie_three <- unused serie with old data!

`seriesOpacity` keeps track of series already seen so that they may
be made invisible:

    1. data.key = serie_two
    2. data.key = serie_thre

--- app/assets/javascripts/d3/dynamic_demand_curve.coffee
+++ app/assets/javascripts/d3/dynamic_demand_curve.coffee
@@ -1,3 +1,48 @@
+# Creates a function which may be used to determine the opacity of each serie in
+# the chart. This is used as a D3 function:
+#
+#   d3.selectAll('selector').style(opacity, seriesOpacity(visible))
+#
+# When a serie is made invisible, it is no longer included in the list of
+# visibleData. This can cause D3 to reuse a g.serie element which was previously
+# assigned to another serie. The initial chart may be:
+#
+#   1. data.key = serie_one
+#   2. data.key = serie_two
+#   3. data.key = serie_three
+#
+# If serie_one is made invisible, D3 will use that SVG element for serie_two,
+# and the element for serie_two is used for serie_three. Since elements are
+# hidden based on their keys, this results in the old serie_three element still
+# being visible:
+#
+#   1. data.key = serie_two
+#   2. data.key = serie_three
+#   3. data.key = serie_three <- unused serie with old data!
+#
+# `seriesOpacity` keeps track of series already seen so that they may be made
+# invisible:
+#
+#   1. data.key = serie_two
+#   2. data.key = serie_three
+#
+# Returns a function.
+seriesOpacity = (visibleSeries) ->
+  seen = {}
+
+  (serieData) ->
+    if seen[serieData.key] then return 0.0
+
+    seen[serieData.key] = true
+
+    # Sets the opacity of invisible series (particularly those whose
+    # values are all zeroes) to avoid rendering artifacts when multiple
+    # such series overlap.
+    if visibleSeries.some((serie) -> serie.get('gquery_key') == serieData.key)
+      1.0
+    else
+      0.0
+
 D3.dynamic_demand_curve =
   View: class extends D3YearlyChartView
     initialize: ->
@@ -142,13 +187,4 @@
       else
         @initialDraw(xScale, yScale, area, line)

-      serie = @svg.selectAll('g.serie')
-        .style('opacity', (data) ->
-          # Sets the opacity of invisible series (particularly those whose
-          # values are all zeroes) to avoid rendering artifacts when multiple
-          # such series overlap.
-          if _.find(legendSeries, (s) -> s.get('gquery_key') == data.key)
-            1.0
-          else
-            0.0
-        )
+      @svg.selectAll('g.serie').style('opacity', seriesOpacity(legendSeries))


Datepicker cleanup

Datepicker cleanup

--- app/assets/javascripts/exo/views/datepicker.js.coffee
+++ app/assets/javascripts/exo/views/datepicker.js.coffee
@@ -7,6 +7,8 @@
     dateFormat: "MM/DD/YYYY"
     firstDayOfWeek: 1
     linkTemplate: null
+
+    DATESTAMP_FORMAT = "YYYYMMDD"

     position:
       my: 'left top',
@@ -24,7 +26,7 @@

       "mousedown .day": (e) ->
         e.preventDefault()
-        @select(e.target.getAttribute("data-datestamp"))
+        @select(moment(e.target.getAttribute("data-datestamp"), DATESTAMP_FORMAT))

     initialize: ->
       @range = []
@@ -96,7 +98,7 @@
             classes.push('today')

            E = $(document.createElement('a')).attr({
-            'data-datestamp': d.format(@dateFormat),
+            'data-datestamp': d.format(DATESTAMP_FORMAT),
             'class': classes.join(' ')
           }).html(d.date())

@@ -136,12 +138,12 @@

       options = _.extend({ silent: false, toggle: true }, options)

-      if date.format(@dateFormat) != @now.format(@dateFormat)
+      if date.format(DATESTAMP_FORMAT) != @now.format(DATESTAMP_FORMAT)
         @showDate(date)

       @clear() if(options.toggle)

-      elem = @$body.find('[data-datestamp="' + date.format(@dateFormat) + '"]').addClass('highlight')
+      elem = @$body.find('[data-datestamp="' + date.format(DATESTAMP_FORMAT) + '"]').addClass('selected')

       if !options.silent
         @trigger('select', date, elem)
@@ -166,5 +168,5 @@

     clear: ->
       @trigger('clear')
-      @$body.children().removeClass('highlight')
+      @$body.children().removeClass('selected')


Fix replace self on load function

Fix replace self on load function

It will now trigger the replace:success event only after the new
new content is loaded.

--- app/assets/javascripts/replace_self_on_load.js.coffee
+++ app/assets/javascripts/replace_self_on_load.js.coffee
@@ -1,5 +1,5 @@
 RunLoop.register ->
   for el in $('[data-replace-self-on-load]')
     url = $(el).data('replace-self-on-load')
-    $(el).removeAttr('data-replace-self-on-load').trigger('replace:success')
-    window.fetchAndReplace(el, url)
+    window.fetchAndReplace el, url, (newEl) =>
+      $(newEl).trigger('replace:success')


プログラムの実行中は画面をブロックするように修正

プログラムの実行中は画面をブロックするように修正

--- app/assets/javascripts/views/main_menu_view.js.coffee
+++ app/assets/javascripts/views/main_menu_view.js.coffee
@@ -11,11 +11,31 @@

     sourceCode = new Smalruby.SourceCode()
     $('#filename').val(sourceCode.get('filename'))
+
+    $.blockUI
+      message:
+        """
+

+
+          プログラムの実行中
+

+

+

+            プログラムの画面に切り替えてください。
+

+
+            Escキーを押すとプログラムが終わります。

+            続きはプログラムが終わってからです♪
+
+

+        """
+      css:
+        border: 'none'
+
     sourceCode.run()
       .done (data) ->
-        if data.length == 0
-          successMessage('実行しました')
-        else
+        $.unblockUI()
+        if data.length > 0
           for errorInfo in data
             do (errorInfo) ->
               msg = "#{errorInfo.row}行"
@@ -23,6 +43,7 @@
                 msg += "、#{errorInfo.column}文字"
               errorMessage(msg + ": #{errorInfo.message}")
       .fail ->
+        $.unblockUI()
         errorMessage('実行できませんでした')

   onReset: (e) ->


something changed and this is how to make it work now

--- tutor/src/screens/teacher-dashboard/duration.cjsx
+++ tutor/src/screens/teacher-dashboard/duration.cjsx
@@ -1,5 +1,5 @@
 React = require 'react'
-moment = require 'moment-timezone'
+moment = require 'moment'
 twix = require 'twix'
 _ = require 'underscore'
 camelCase = require 'lodash/camelCase'


local_hub -- fix a relatively harmless typo.

--- salvus/local_hub.coffee
+++ salvus/local_hub.coffee
@@ -1242,7 +1242,7 @@
                 finish(session)

     add_session_to_cache: (opts) =>
-        opt = defaults opts,
+        opts = defaults opts,
             session    : required
             project_id : undefined
             timeout    : undefined   # or a time in seconds


убран отладочный вывод

убран отладочный вывод

--- app/assets/javascripts/block.coffee
+++ app/assets/javascripts/block.coffee
@@ -2,7 +2,6 @@
 	constructor: ($b, params, name) ->
 		@$node = $b
 		@name = name
-		console.log(@name)
 		@id = $$.utils.guid()
 		@params = params
 		#$.extend this, decl.methods


no prime citekey cache on first install

no prime citekey cache on first install

--- chrome/content/zotero-better-bibtex/keymanager.coffee
+++ chrome/content/zotero-better-bibtex/keymanager.coffee
@@ -61,8 +61,9 @@
     # select non-note, non-attachment items that don't have a cached key, and generate one, to make sure the cache is
     # complete. Should only run after first installation, as after that all new items or item changes will generate
     # keys, but better safe than sorry.
-    for row in Zotero.DB.query('select itemID from items where itemTypeID not in (0, 14) and itemID not in (select itemID from betterbibtex.keys)')
-      @get({itemID: row.itemID})
+    # TODO: this works locally but fails on Travis -- investigate later
+    #for row in Zotero.DB.query('select itemID from items where itemTypeID not in (0, 14) and itemID not in (select itemID from betterbibtex.keys)')
+    #  @get({itemID: row.itemID})

   reset: ->
     Zotero.DB.query('delete from betterbibtex.keys')


Make 'atom' command w/ no args reopen previous windows

--- src/browser/main.coffee
+++ src/browser/main.coffee
@@ -133,7 +133,6 @@
   safeMode = args['safe']
   apiPreviewMode = args['one']
   pathsToOpen = args._
-  pathsToOpen = [executedFrom] if executedFrom and pathsToOpen.length is 0
   test = args['test']
   specDirectory = args['spec-directory']
   newWindow = args['new-window']


:memo: Fix comment typos

:memo: Fix comment typos

--- src/browser/main.coffee
+++ src/browser/main.coffee
@@ -77,8 +77,8 @@
 setupAtomHome = ->
   return if process.env.ATOM_HOME

-  # Use install-relative .atom path on Windows when using the installer version
-  # in a non-default version. This enables an easy portable version.
+  # Use relative .atom path on Windows when using the installer version
+  # in a non-default location. This enables an easy portable version.
   if process.platform is 'win32'
     SquirrelUpdate = require './squirrel-update'
     if SquirrelUpdate.existsSync()


Revert moving parse of command line

Revert moving parse of command line

to the first line of start function because the move is not necessary
without having the code for using a command line parameter to set Atom to
portable mode.  This belongs on that branch and in that pull request.

--- src/browser/main.coffee
+++ src/browser/main.coffee
@@ -12,14 +12,14 @@
 console.log = require 'nslog'

 start = ->
-  args = parseCommandLine()
-
   setupAtomHome()
   setupCompileCache()
   return if handleStartupEventWithSquirrel()

   # NB: This prevents Win10 from showing dupe items in the taskbar
   app.setAppUserModelId('com.squirrel.atom.atom')
+
+  args = parseCommandLine()

   addPathToOpen = (event, pathToOpen) ->
     event.preventDefault()


Fix vote hover race condition

Fix vote hover race condition

--- app/assets/javascripts/votes.coffee
+++ app/assets/javascripts/votes.coffee
@@ -1,10 +1,10 @@
-$(".votable").delegate ".vote[data-role=create]", "ajax:success", (e, data, textStatus, jqXHR) ->
+$('.votable').delegate '.vote[data-role=create]', 'ajax:success', (e, data, textStatus, jqXHR) ->
   voteCallback.call(this, data)
-$(".votable").delegate ".vote[data-role=delete]", "ajax:success", (e, data, textStatus, jqXHR) ->
+$('.votable').delegate '.vote[data-role=delete]', 'ajax:success', (e, data, textStatus, jqXHR) ->
   unvoteCallback.call(this, data)
-$(".votable").delegate ".vote", "ajax:error", (e, data, textStatus, jqXHR) ->
+$('.votable').delegate '.vote', 'ajax:error', (e, data, textStatus, jqXHR) ->
   voteErrorCallback.call(this, data)
-$(".votable").delegate ".vote", "ajax:beforeSend", (e, data, textStatus, jqXHR) ->
+$('.votable').delegate '.vote', 'ajax:beforeSend', (e, data, textStatus, jqXHR) ->
   if $('body').hasClass('loading')
     return false
   $('body').addClass('loading')
@@ -14,9 +14,12 @@
   $('body').removeClass('loading')
   $(this).attr('data-role','delete')
   $(this).attr('action',"/votes/#{data.vote.id}")
-  $(this).append($('<input name="_method" type="hidden" value="delete" />'))
-  $(this).one 'mouseout', ->
-    $(this).find('input.vote').removeClass("vote").addClass("unvote")
+  $(this).append($('<input name=_method type=hidden value=delete />'))
+  if($(this).data('hover'))
+    $(this).one 'mouseout', ->
+      $(this).find('input.vote').removeClass('vote').addClass('unvote')
+  else
+    $(this).find('input.vote').removeClass('vote').addClass('unvote')
   id = $(this).parent().data('id')
   $(".vote-count[data-id=#{id}]").text(parseInt($(".vote-count[data-id=#{id}]").text()) + 1 )

@@ -24,10 +27,13 @@
 window.unvoteCallback = (data) ->
   $('body').removeClass('loading')
   $(this).attr('data-role','create')
-  $(this).attr('action',"/votes")
+  $(this).attr('action','/votes')
   $(this).find('input[value=delete]').remove()
-  $(this).one 'mouseout', ->
-    $(this).find('input.unvote').removeClass("unvote").addClass("vote")
+  if($(this).data('hover'))
+    $(this).one 'mouseout', ->
+      $(this).find('input.unvote').removeClass('unvote').addClass('vote')
+  else
+    $(this).find('input.unvote').removeClass('unvote').addClass('vote')
   id = $(this).parent().data('id')
   $(".vote-count[data-id=#{id}]").text(parseInt($(".vote-count[data-id=#{id}]").text()) - 1 )

@@ -35,4 +41,9 @@
   $('body').removeClass('loading')
   $(".flash").append($("
#{data.statusText}
"))

+$(".vote").hover (->
+  $(this).data 'hover', true
+), ->
+  $(this).data 'hover', false

+


sidebar: add workspace notification jewel

--- client/Main/navigation/navigationworkspaceitem.coffee
+++ client/Main/navigation/navigationworkspaceitem.coffee
@@ -11,9 +11,10 @@

     data = @getData()

-    @updatePartial """
+    @setPartial """

       #{data.title}
+      0
     """


send session vars to front-end components. they are otherwise not shown to end user, but i'm mostly treating session vars like cookies anyway

--- src/render/renderPageWithComponent.coffee
+++ src/render/renderPageWithComponent.coffee
@@ -10,6 +10,9 @@
   (req, res, next) ->
     res.__render = res.render
     res.render = (componentPath, options = {}) ->
+      defaults =
+        isUser: req.isUser
+      options = _.extend defaults, req.session, options
       unless options.isUser?
         options.isUser = req.isUser
       if /:/.test componentPath


[backport] Fixing config in navurls

[backport] Fixing config in navurls

--- app/coffee/modules/base/urls.coffee
+++ app/coffee/modules/base/urls.coffee
@@ -30,7 +30,7 @@

     constructor: (@config) ->
         @.urls = {}
-        @.mainUrl = config.get("api")
+        @.mainUrl = @config.get("api")

     update: (urls) ->
         @.urls = _.merge(@.urls, urls)


Return a promise from applyTo if jQuery is loaded

--- app/scripts/lib/bounce/index.coffee
+++ app/scripts/lib/bounce/index.coffee
@@ -62,6 +62,10 @@
     elements = [elements] unless elements.length
     prefixes = @getPrefixes()

+    deferred = null
+    if window.jQuery and window.jQuery.Deferred
+      deferred = new window.jQuery.Deferred()
+
     for element in elements
       for prefix in prefixes.animation
         css = [@name, "#{@duration}ms", "linear", "both"]
@@ -72,9 +76,10 @@
       setTimeout (=>
         @remove() if options.remove
         options.onComplete?()
+        deferred.resolve() if deferred
       ), @duration

-    this
+    deferred

   remove: ->
     @styleElement?.remove()


debug

debug

--- test/controllers/resourceTest.coffee
+++ test/controllers/resourceTest.coffee
@@ -170,6 +170,7 @@
           App.Shop.create title: ['a', 'g', 'h'], (err, model) =>
             agent.get(":3001/shops.json?title=a")
               .end (res) ->
+                console.log res
                 res.body.length.should.equal 2

                 done()


added test

added test

--- test/crystal/validate/exports.coffee
+++ test/crystal/validate/exports.coffee
@@ -133,6 +133,25 @@
         (-> new crystal config)
           .should.not.throw "`exports.ConfigGenerator.spec` must be a `object` or `string`, not a `array`."

+      it 'should allow multiple mixed specs', () ->
+        # object
+        config = {
+          exports: {
+            ConfigGenerator: {
+              spec: [
+                'TestSpec1',
+                {
+                  name: 'Test'
+                  description: 'Description'
+                }
+              ]
+              type: 'generator'
+            }
+          }
+        }
+        (-> new crystal config)
+          .should.not.throw "`exports.ConfigGenerator.spec` must be a `object` or `string`, not a `array`."
+
       it 'should not require a transformer', () ->
         # object
         config = {


Don't call twttr in tests

Don't call twttr in tests

--- core/app/backbone/views/evidence/react_comment.coffee
+++ core/app/backbone/views/evidence/react_comment.coffee
@@ -30,7 +30,9 @@
       _i ["icon-twitter"]

   componentDidMount: ->
-    twttr.widgets.load();
+    return if Factlink.Global.environment == 'test' # Twitter not loaded in tests
+
+    twttr.widgets.load()

 window.ReactComment = React.createBackboneClass
   displayName: 'ReactComment'


Add force option

Add force option

--- src/browser/squirrel-update.coffee
+++ src/browser/squirrel-update.coffee
@@ -42,15 +42,15 @@
       callback(error)

   installFileMenu = ->
-    args = [fileKeyPath, '/ve', '/d', 'Open with Atom']
+    args = [fileKeyPath, '/ve', '/d', 'Open with Atom', '/f']
     spawnReg args, (error) ->
       return callback(error) if error?

-      args = [fileKeyPath, '/v', 'Icon', '/d', process.execPath]
+      args = [fileKeyPath, '/v', 'Icon', '/d', process.execPath, '/f']
       spawnReg args, (error) ->
         return callback(error) if error?

-        args = ["#{fileKeyPath}\\command", '/ve', '/d', process.execPath]
+        args = ["#{fileKeyPath}\\command", '/ve', '/d', process.execPath, '/f']
         spawnReg(args, callback)

   installFileMenu(callback)


Don't write to path when reading is blocked

Closes #5092

--- src/browser/squirrel-update.coffee
+++ src/browser/squirrel-update.coffee
@@ -81,6 +81,10 @@
   spawnReg ['query', environmentKeyPath, '/v', 'Path'], (error, stdout) ->
     if error?
       if error.code is 1
+        # Don't overwrite path when reading value is disabled
+        if stdout.indexOf('ERROR: Registry editing has been disabled by your administrator.') isnt -1
+          return callback(error)
+
         # The query failed so the Path does not exist yet in the registry
         return callback(null, '')
       else


Improve brazilian regex

Improve brazilian regex


Give priority to teacher link over student

This way users that are both a teacher and  student on a course will go
to the teacher location

--- src/components/course-listing.cjsx
+++ src/components/course-listing.cjsx
@@ -11,7 +11,7 @@
 # Called once the store is loaded
 # checks the course and roles and will redirect if there is only a single course and role
 DisplayOrRedirect = (transition, callback) ->
-  courses = CourseListingStore.allCourses() or []
+  courses = CourseListingStore.allValidCourses() or []
   [course] = courses
   if courses.length is 1 and course.roles?.length is 1
     roleType = courses[0].roles[0].type
@@ -59,12 +59,6 @@
       isStudent = _.find roles, (role) -> role.type is 'student'
       isTeacher = _.find roles, (role) -> role.type is 'teacher'

-      if isStudent or not isTeacher # HACK since a student does not currently have a role
-        courseLink = <Router.Link
-          className='tutor-course-item'
-          to='viewStudentDashboard'
-          params={{courseId}}>{course.name}</Router.Link>
-
       if isTeacher
         if courseLink?
           altLink = <Router.Link
@@ -77,7 +71,14 @@
             className='tutor-course-item'
             to={to}
             params={{courseId}}>{course.name}</Router.Link>
-
+      else if isStudent
+        courseLink = <Router.Link
+          className='tutor-course-item'
+          to='viewStudentDashboard'
+          params={{courseId}}>{course.name}</Router.Link>
+      else
+        console.warn?("BUG: User is not a teacher or a student on course id: #{course.id}")
+        return null
       courseDataProps = @getCourseDataProps(courseId)
       <BS.Row>
         <BS.Col {...courseDataProps} className='tutor-booksplash-course-item' xs={12}>
@@ -87,7 +88,7 @@
       </BS.Row>

   render: ->
-    courses = CourseListingStore.allCourses() or []
+    courses = CourseListingStore.allValidCourses() or []
     body = if courses.length

{@renderCourses(courses)}

     else


Make tests pass on async deactivatePackage

--- spec/themes-panel-spec.coffee
+++ spec/themes-panel-spec.coffee
@@ -48,14 +48,20 @@
       for child in panel.refs.uiMenu.children
         child.selected = child.value is 'atom-light-ui'
         child.dispatchEvent(new Event('change', {bubbles: true}))
-      expect(atom.config.get('core.themes')).toEqual ['atom-light-ui', 'atom-dark-syntax']
+      waitsFor ->
+        reloadedHandler.callCount is 2
+      runs ->
+        expect(atom.config.get('core.themes')).toEqual ['atom-light-ui', 'atom-dark-syntax']

   describe "when a syntax theme is selected", ->
     it "updates the 'core.themes' config key with the selected syntax theme", ->
       for child in panel.refs.syntaxMenu.children
         child.selected = child.value is 'atom-light-syntax'
         child.dispatchEvent(new Event('change', {bubbles: true}))
-      expect(atom.config.get('core.themes')).toEqual ['atom-dark-ui', 'atom-light-syntax']
+      waitsFor ->
+        reloadedHandler.callCount is 2
+      runs ->
+        expect(atom.config.get('core.themes')).toEqual ['atom-dark-ui', 'atom-light-syntax']

   describe "when the 'core.config' key changes", ->
     it "refreshes the theme menus", ->


Throw error when activating incompatible package

--- src/package.coffee
+++ src/package.coffee
@@ -96,6 +96,8 @@
     Q.all([@grammarsPromise, @scopedPropertiesPromise, @activationDeferred.promise])

   activateNow: ->
+    throw new Error("#{@name}@#{@metadata.version} is not compatible with this version of Atom") unless @isCompatible()
+
     try
       @activateConfig()
       @activateStylesheets()


social: send workspace_created event for newly created workspaces

--- workers/social/lib/social/models/workspace.coffee
+++ workers/social/lib/social/models/workspace.coffee
@@ -40,7 +40,7 @@
       instance          :
         delete          : signature Function
     sharedEvents        :
-      static            : []
+      static            : ['messageBusEvent']
       instance          : ['updateInstance']

@@ -83,6 +83,13 @@
           return

         delegate.emit 'NewWorkspaceCreated', workspace
+        message =
+          accountId: delegate.socialApiId
+          name: name
+          slug: slug
+
+        @emit 'messageBusEvent', {type: "social.workspace_created", message: message}
+
         return callback null, workspace

     if data.isDefault
@@ -208,7 +215,7 @@
           return callback err  if err

           {nickname}     = account.profile
-
+
           data           =
             name         : 'My Workspace'
             isDefault    : yes


Moved to separate methods for better readability

--- core/app/backbone/factlink/collection.coffee
+++ core/app/backbone/factlink/collection.coffee
@@ -3,8 +3,14 @@
   constructor: (args...) ->
     super args...
     @_loading = false
-    @on 'request', (collection) => @_loading = true if collection == this
-    @on 'sync', (collection) => @_loading = false if collection == this
+    @on 'request', @_onRequest, @
+    @on 'sync', @_onSync, @
+
+  _onRequest: (collection) ->
+    @_loading = true if collection == this
+
+  _onSync: (collection) ->
+    @_loading = false if collection == this

   loading: -> @_loading


KodingClient: Refactor check key function

--- server/lib/server/index.coffee
+++ server/lib/server/index.coffee
@@ -107,22 +107,12 @@
 app.get "/-/auth/check/:key", (req, res)->
   {key} = req.params

-  console.log "checking for key"
-  authCheckKey key, (ok, result) ->
-    if not ok
-      console.log "key is valid: #{result}" #keep up the result to us
-      res.send 401, authTemplate "Key is not valid: '#{key}'"
-      return
-
-    {JKodingKey} = koding.models
-    JKodingKey.fetchKey
-      key     : key
-    , (err, kodingKey)=>
-      if err or not kodingKey
-        res.send 401, authTemplate "Key doesn't exist"
-        return
-
-      res.send 200, {result: 'key is added successfully'}
+  {JKodingKey} = koding.models
+  JKodingKey.checkKey
+    key     : key
+  , (err, status)=>
+    return res.send 401, authTemplate "Key doesn't exist" unless status
+    res.send 200, {result: 'key is added successfully'}

 app.get "/-/auth/register/:hostname/:key", (req, res)->
   {key, hostname} = req.params


Team: Hide button loader if form is not succeed

--- client/landing/site.landing/coffee/team/forms/teamjoinwithinvitedaccountform.coffee
+++ client/landing/site.landing/coffee/team/forms/teamjoinwithinvitedaccountform.coffee
@@ -28,6 +28,7 @@
       @emit 'FormNeedsToBeChanged', no, no

     @on 'FormValidationFailed', @button.bound 'hideLoader'
+    @on 'FormSubmitFailed', @button.bound 'hideLoader'

   submit: (formData) ->


payment: subscription check endpoint returns group subscription if group is not "koding"

--- server/lib/server/index.coffee
+++ server/lib/server/index.coffee
@@ -162,8 +162,7 @@
             planMap = {}
             planMap[plan.planCode] = plan  for plan in plans

-            targetOptions = selector: planCode: $in: (plan.planCode for plan in plans)
-            account.fetchSubscriptions null, {targetOptions}, (err, subscriptions) ->
+            kallback = (err, subscriptions) ->
               return res.send 401, err: "NO_SUBSCRIPTION"  if err or not subscriptions

               freeSubscription = null
@@ -180,6 +179,13 @@
                 res.send 200, planId: plan.planCode, planName: plan.title
               else
                 res.send 401, err: "NO_SUBSCRIPTION"
+
+            if group.slug is "koding"
+              targetOptions = selector: planCode: $in: (plan.planCode for plan in plans)
+              account.fetchSubscriptions null, {targetOptions}, kallback
+            else
+              group.fetchSubscriptions kallback
+

 app.get "/-/8a51a0a07e3d456c0b00dc6ec12ad85c", require './__notify-users'


Fix observeBuffers

Fix observeBuffers

--- src/project.coffee
+++ src/project.coffee
@@ -366,7 +366,7 @@
     message = 'Use Project::observeBuffers instead'
     message += '; Project::observeBuffers no longer accepts a subscriber parameter. Call `subscriber.subscribe(atom.project.observeBuffers(cb))`' if subscriber?

-    disposable = observeBuffers(callback)
+    disposable = @observeBuffers(callback)
     subscriber.subscribe(disposable) if subscriber?
     disposable


Update documentation

Update documentation

--- src/project.coffee
+++ src/project.coffee
@@ -137,7 +137,7 @@
   #
   # * filePath:
   #   The {String} path of the file to associate with
-  # * editorOptions:
+  # * options:
   #   Options that you can pass to the {Editor} constructor
   #
   # Returns a promise that resolves to an {Editor}.


Don't send atom.io token to github.com

Don't send atom.io token to github.com

Previously this was doable since the tokens worked in both places

--- src/publish.coffee
+++ src/publish.coffee
@@ -92,31 +92,25 @@
   #            or the maximum numbers of requests for the tag have been made.
   #            No arguments are passed to the callback when it is invoked.
   waitForTagToBeAvailable: (pack, tag, callback) ->
-    @getToken (error, token) =>
-      if error?
-        callback(error)
-        return
-
-      retryCount = 5
-      interval = 1000
-      requestSettings =
-        url: "https://api.github.com/repos/#{Packages.getRepository(pack)}/tags"
-        json: true
-        proxy: process.env.http_proxy || process.env.https_proxy
-        headers:
-          'User-Agent': "AtomApm/#{require('../package.json').version}"
-          authorization: "token #{token}"
-
-      requestTags = ->
-        request.get requestSettings, (error, response, tags=[]) ->
-          if response.statusCode is 200
-            for {name}, index in tags when name is tag
-              return callback()
-          if --retryCount <= 0
-            callback()
-          else
-            setTimeout(requestTags, interval)
-      requestTags()
+    retryCount = 5
+    interval = 1000
+    requestSettings =
+      url: "https://api.github.com/repos/#{Packages.getRepository(pack)}/tags"
+      json: true
+      proxy: process.env.http_proxy || process.env.https_proxy
+      headers:
+        'User-Agent': "AtomApm/#{require('../package.json').version}"
+
+    requestTags = ->
+      request.get requestSettings, (error, response, tags=[]) ->
+        if response.statusCode is 200
+          for {name}, index in tags when name is tag
+            return callback()
+        if --retryCount <= 0
+          callback()
+        else
+          setTimeout(requestTags, interval)
+    requestTags()

   # Does the given package already exist in the registry?
   #


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/clamp_bounds.coffee
+++ src/clamp_bounds.coffee
@@ -6,5 +6,6 @@
     height: 32

   self.bind "update", ->
+    de
     I.x = I.x.clamp(I.width / 2, App.width - I.width / 2)
     I.y = I.y.clamp(I.height / 2, App.height - I.height / 2)


ClampBounds docs

ClampBounds docs

--- src/clamp_bounds.coffee
+++ src/clamp_bounds.coffee
@@ -10,7 +10,7 @@
     width: 5
     height: 17

-  # put the player outside the
+  # put the player outside the viewport
   player.I.x = -400
   player.update()
 </pre></code>


ClampBounds docs

ClampBounds docs

--- src/clamp_bounds.coffee
+++ src/clamp_bounds.coffee
@@ -13,7 +13,7 @@
   # put the player outside the viewport
   player.I.x = -400

-  # updat t
+  # update the pla
   player.update()
 </pre></code>


Fix checkbox selector, closes #7

Fix checkbox selector, closes #7

--- src/coffee/happy.coffee
+++ src/coffee/happy.coffee
@@ -26,7 +26,7 @@
 	thicknessify: (input, happy_input) ->
 		if input.classList.contains('thin')
 			happy_input.classList.add('thin')
-
+

 	setNames: (input, happy_input) ->
 		happy_input.setAttribute('data-name', input.getAttribute('name'))
@@ -135,6 +135,9 @@
 		e.preventDefault()

 		selector = 'input[type=checkbox][name="'+happy_input.getAttribute('data-name')+'"]'
+		value = happy_input.getAttribute('data-value')
+		if typeof(value != 'undefined') and value != false and value != null
+			selector += '[value="' + value + '"]'
 		input = document.querySelector(selector)

 		if input
@@ -162,6 +165,9 @@

 	checkCheckboxState: (input) =>
 		selector = '.happy-checkbox[data-name="'+input.getAttribute('name')+'"]'
+		value = input.getAttribute('value')
+		if typeof(value != 'undefined') and value != false and value != null
+			selector += '[data-value="' + value + '"]'
 		element = document.querySelector(selector)

 		if element


Accounts: Fix tabView issue

Accounts: Fix tabView issue

--- client/Account/AppController.coffee
+++ client/Account/AppController.coffee
@@ -76,7 +76,7 @@
     mainView.addSubView navView = @navController.getView()

     mainView.addSubView @tabView = new KDTabView
-      tabHandleContainer : new KDCustomHTMLView
+      hideHandleContainer : yes

     @tabView.addPane @createTab items.personal.items.first


KDFramework: KDView::destroy fixed

KDFramework: KDView::destroy fixed

--- client/Framework/core/KDView.coffee
+++ client/Framework/core/KDView.coffee
@@ -371,8 +371,9 @@
     @destroySubViews()  if @getSubViews().length > 0

     # instance drops itself from its parent's subviews array
-    if @parent?.subViews and index = @parent.subViews.indexOf @ >= 0
-        @subViews.splice index, 1
+
+    if @parent?.subViews and (index = @parent.subViews.indexOf @) >= 0
+        @parent.subViews.splice index, 1
         @unsetParent()

     # instance removes itself from DOM


Update error handlings.

Update error handlings.

--- src/scraper.coffee
+++ src/scraper.coffee
@@ -15,7 +15,6 @@
 HTTPS = "https://"
 HTTP = "http://"

-READY = "ready"

 check_like_score = (value) ->
   ###
@@ -46,7 +45,7 @@
       url.substring(0, HTTP.length) isnt HTTP
     url = BASE_URL + url

-  new Promise (resolve, inject) ->
+  new Promise (resolve, reject) ->

     phantom.create().then (ph) ->

@@ -57,7 +56,7 @@
           .then (status) ->
             # Check loaded page has header section.
             # If not, wait more 1000 msec.
-            new Promise (resolve, _) ->
+            new Promise (resolve, reject) ->
               do check_header = ->
                 page.evaluate ->
                   document.getElementsByClassName(
@@ -67,6 +66,9 @@
                     resolve status
                   else
                     setTimeout check_header, 1000
+
+                .catch (reason) ->
+                  reject reason

           .then (status) ->
             page.evaluate ->
@@ -91,22 +93,25 @@
                 for read_more in document.getElementsByClassName("read-more")
                   read_more.firstElementChild.click()

-                document.body.dataset.youtubeCommentScraper = READY
+                document.body.dataset.youtubeCommentScraper = "ready"

           .then ->
             # Check data-youtube-comment-scraper is ready, which means all pages
             # are loaded and all collapsed comments are expanded.
             # If not, wait more 1000 msec.
-            new Promise (resolve, _) ->
+            new Promise (resolve, reject) ->
               do get_body = ->
                 page.evaluate ->
-                  if document.body.dataset.youtubeCommentScraper is READY
+                  if document.body.dataset.youtubeCommentScraper is "ready"
                     document.body.innerHTML
                 .then (html) ->
                   if html
                     resolve html
                   else
                     setTimeout get_body, 1000
+
+                .catch (reason) ->
+                  reject reason

           .then (html) ->
             $ = cheerio.load html
@@ -140,3 +145,10 @@
             page.close()
             ph.exit()
             reject reason
+
+      .catch (resaon) ->
+        ph.exit()
+        reject reason
+
+    .catch (reason) ->
+      reject reason


Remove tabs in the script help comments

Remove tabs in the script help comments

--- src/sensors.coffee
+++ src/sensors.coffee
@@ -7,11 +7,11 @@
 #   HUBOT_SENSORS_FOLDER - Folder of the sensors to be loaded
 #
 # Commands:
-#   hubot sensors list                    - List of loaded sensors
-#   hubot sensors list enabled            - List of enabled sensors
-#   hubot sensors list disabled           - List of disabled sensors
-#   hubot sensors enable <sensor_name>    - Enable a disabled sensor
-#   hubot sensors disable <sensor_name>   - Disable a enabled sensor
+#   hubot sensors list - List of loaded sensors
+#   hubot sensors list enabled - List of enabled sensors
+#   hubot sensors list disabled - List of disabled sensors
+#   hubot sensors enable <sensor_name> - Enable a disabled sensor
+#   hubot sensors disable <sensor_name> - Disable a enabled sensor
 #
 # Author:
 #   basoko


refatora método de troca de classe

refatora método de troca de classe

--- lib/assets/javascripts/do/rails/helpers/jquery.coffee
+++ lib/assets/javascripts/do/rails/helpers/jquery.coffee
@@ -6,20 +6,17 @@

 $.fn.replaceToggleClass = (cls1, cls2)->
-  obj = $(this)
+  self = $(this)

-  obj.each (i)->
-    current = obj.item(i)
-    if current.classList.contains(cls1)
-      $(current).removeClass cls1
-      $(current).addClass cls2
-    else if current.classList.contains(cls2)
-      $(current).removeClass cls2
-      $(current).addClass cls1
+  if self.get(0).classList.contains(cls1)
+    # os métodos próprios do jquery mantém o controle da visibilidade e existência
+    self.removeClass cls1
+    self.addClass cls2
+  else if self.get(0).classList.contains(cls2)
+    self.removeClass cls2
+    self.addClass cls1

-  # setTimeout ->, interval || 0
-
-  obj
+  self

 $.fn.replaceClass = (sought, substitute, interval)->
   obj = $(this)
@@ -57,3 +54,4 @@
   item: (index)->
     obj = $(this)
     obj.get index
+


Move conditions onto separate lines

Move conditions onto separate lines

--- lib/guard/jasmine/phantomjs/src/guard-reporter.coffee
+++ lib/guard/jasmine/phantomjs/src/guard-reporter.coffee
@@ -39,10 +39,11 @@
         my = this
         console[level] = ->
             args = Array.prototype.slice.call(arguments, 0)
-            return if args[0] && args[0].toString && args[0].toString().match( ConsoleCapture.DOT_REPORTER_MATCH )
+            return if args[0] &&
+                args[0].toString &&
+                args[0].toString().match( ConsoleCapture.DOT_REPORTER_MATCH )
             my.captured.push( [ level ].concat( args ) )
             ConsoleCapture.original_levels[ level ].apply( ConsoleCapture.original, arguments )
-

 # Implements a Jasmine reporter
 class GuardReporter


randomWord uses repeatable random functions

--- test/types/randomWord.coffee
+++ test/types/randomWord.coffee
@@ -1,7 +1,7 @@
 fs = require 'fs'

 # Generate a random int 0 <= k < n
-randomInt = (n) -> Math.floor(Math.random() * n)
+{randomInt} = require '../helpers'

 # Return a random word from a corpus each time the method is called
 module.exports = do ->


Use filter/map in watch task

Use filter/map in watch task

--- tgui/gulp/tasks/watch.coffee
+++ tgui/gulp/tasks/watch.coffee
@@ -10,9 +10,8 @@

 module.exports = ->
-  paths = []
-  for i,path of p
-    paths.push "#{path.dir}**" if path.dir
-
-  gulp.watch paths, debounce(gulp.series(clean, build, reload), 1000)
+  gulp.watch(
+    Object.keys(p).filter((path) -> p[path].dir?).map((path) -> p[path].dir + "**"),
+    debounce gulp.series(clean, build, reload), 1000
+  )
 module.exports.displayName = "watch"


Run not under WSH

Run not under WSH

--- src/sys/wsh.coffee
+++ src/sys/wsh.coffee
@@ -1 +1 @@
-module.exports = WScript
+module.exports = WScript ? null


style(textlex): remove superfluous parentheses

--- src/textlex.coffee
+++ src/textlex.coffee
@@ -71,7 +71,7 @@

     fileContents ?= fs.readFileSync(filePath, 'utf8') if filePath
     grammar = @registry.grammarForScopeName scopeName
-    grammar ?= pickGrammar.selectGrammar(@registry, filePath, fileContents)
+    grammar ?= pickGrammar.selectGrammar @registry, filePath, fileContents
     lineTokens = grammar.tokenizeLines fileContents

     # Remove trailing newline


Sitewide: continue at a given route upon refresh logic

--- client/Main/maincontroller/maincontroller.coffee
+++ client/Main/maincontroller/maincontroller.coffee
@@ -157,9 +157,7 @@
         # window location path is set to last route to ensure visitor is not
         # redirected to another page
         @utils.defer ->
-          firstRoute = KD.getSingleton("router").visitedRoutes.first
-
-          firstRoute = KD.getSingleton("router").visitedRoutes.first
+          firstRoute = localStorage?.routeToBeContinued or KD.getSingleton("router").visitedRoutes.first
           if firstRoute and /^\/(?:Reset|Register|Verify|Confirm)\//.test firstRoute
             firstRoute = "/Activity"


Fix failing protractor

Fix failing protractor

--- client/angular/test/protractor/group_spec.coffee
+++ client/angular/test/protractor/group_spec.coffee
@@ -250,7 +250,7 @@
                  '.group-page-actions__leave-group',
                  '.leave-group-form__submit')
       page.expectFlash('You have left this group')
-      page.expectText('.dashboard-page__no-groups', "You don't have any recent threads because you are not a member of any groups.")
+      page.expectText('.dashboard-page__no-groups', "Start or join a group to see threads")

     it 'prevents last coordinator from leaving the group', ->
       # click leave group from the group actions downdown


Stack Catalog: Redirect user to "/IDE" when any stack is deleted.

--- client/app/lib/environment/yourstacksview.coffee
+++ client/app/lib/environment/yourstacksview.coffee
@@ -44,7 +44,7 @@

     listView.on 'ModalDestroyRequested', @bound 'destroyModal'

-    { computeController, appManager } = kd.singletons
+    { computeController, appManager, router } = kd.singletons

     listView.on 'StackDeleteRequested', (stack) =>

@@ -54,8 +54,9 @@
         new kd.NotificationView
           title : 'Stack deleted'

-        computeController.reset yes
+        computeController.reset yes, -> router.handleRoute '/IDE'
         @destroy()
+

     listView.on 'StackReinitRequested', (stack) =>
       computeController.once 'RenderStacks', => @destroyModal yes, yes


Code refactoring

Code refactoring

--- client/ide/lib/workspace/idelayoutmanager.coffee
+++ client/ide/lib/workspace/idelayoutmanager.coffee
@@ -206,7 +206,7 @@
     panes = []

     for item in snapshot when item.type is 'split'
-      IDELayoutManager.findPanesFromArray panes, item
+      IDELayoutManager.findPanesFromArray panes, item.views

     return panes

@@ -215,15 +215,15 @@
    * Find panes from array.
    *
    * @param {Array} panes  Referenced parameter
-   * @param {Object} item
-  ###
-  @findPanesFromArray: (panes, item) ->
-
-    return  unless item.views.length
-
-    if item.views.first.context # if items are a pane
-      for pane in item.views    # collect panes
+   * @param {Array} views
+  ###
+  @findPanesFromArray: (panes, views) ->
+
+    return  unless views?.length
+
+    if views.first.context # if items are a pane
+      for pane in views    # collect panes
         panes.push pane
     else
-      for subView in item.views
+      for subView in views
         IDELayoutManager.findPanesFromArray panes, subView # recall itself


work on kite-amqp

work on kite-amqp

--- client/landing/app/MainApp/account-mixins.coffee
+++ client/landing/app/MainApp/account-mixins.coffee
@@ -30,20 +30,32 @@
                 messageHandler.bind channel, kiteName
               )
             messageString = JSON.stringify(scrubbed)
-            console.log {messageString}
-            channel.emit "client-message", messageString
+            onReady channel, ->
+              window.sss = channel
+              channel.publish messageString

       response = (kiteName, method, args) ->
         scrub method, args, (scrubbed) ->
           fetchChannel kiteName, (channel)=>
             channel.emit "client-message", JSON.stringify(scrubbed)

-      ready =->
-        console.log 'ready', arguments
+      readyChannels = {}
+
+      onReady =
+        (channel, callback)->
+          {name} = channel
+          if readyChannels[name] then callback()
+          else
+            readyChannels[name] = channel
+            channel.once 'ready', callback
+
+      ready =(resourceName)->
+        @exchange = resourceName
+        @emit 'ready'

       messageHandler =(kiteName, args) ->
         if args.method is 'ready'
-          callback = ready
+          callback = ready.bind this
         else
           callback = localStore.get(args.method)
         scrubber = new Scrubber localStore
@@ -52,14 +64,19 @@
             remoteStore.add callbackId, ->
               response kiteName, callbackId, [].slice.call(arguments)
           remoteStore.get callbackId
-        callback.apply @, unscrubbed
+        callback.apply this, unscrubbed

-      getChannelName =(kiteName)->
-        delegate  = KD.whoami()
-        nickname  = delegate?.profile.nickname ?
-                    if delegate.guestId then "Guest #{delegate.guestId}" ?
-                    'unknown'
-        return "#{Bongo.createId 128}.#{nickname}.kite-#{kiteName}"
+      getChannelName =do ->
+        namesCache = {}
+        (kiteName)->
+          return namesCache[kiteName]  if namesCache[kiteName]
+          delegate  = KD.whoami()
+          nickname  = delegate?.profile.nickname ?
+                      if delegate.guestId then "Guest #{delegate.guestId}" ?
+                      'unknown'
+          channelName = "#{Bongo.createId 128}.#{nickname}.kite-#{kiteName}"
+          namesCache[kiteName] = channelName
+          return channelName

       fetchChannel =(kiteName, callback)->
         channelName = getChannelName(kiteName)
@@ -67,7 +84,6 @@
           channel = KD.remote.mq.subscribe channelName
           channels[channelName] = channel
           channel.on 'broker.subscribed', -> callback channel
-          channel.exchange = kiteName
           channel.setAuthenticationInfo
             serviceType : 'kite'
             name        : "kite-#{kiteName}"


Add iteration methods to ObservableArray

--- src/observable/observable_array.coffee
+++ src/observable/observable_array.coffee
@@ -156,17 +156,46 @@

     @_lastPatch = patch

-  cls = @
-  [
-    'forEach'
-    'map'
-    'every'
-    'some'
-    'filter'
-    'reduce'
-    'reduceRight'
-  ].forEach (method) ->
-    cls::[method] = -> @_data[method] arguments...
+  forEach: (fn, thisObject) ->
+    fn = fn.bind thisObject if thisObject
+    fn @get(i), i for i in [0...@length] by 1
+    null
+
+  map: (fn, thisObject) ->
+    fn = fn.bind thisObject if thisObject
+    (fn @get(i), i for i in [0...@length] by 1)
+
+  every: (fn, thisObject) ->
+    fn = fn.bind thisObject if thisObject
+    return false for i in [0...@length] by 1 when !fn @get(i), i
+    true
+
+  some: (fn, thisObject) ->
+    fn = fn.bind thisObject if thisObject
+    return true for i in [0...@length] by 1 when fn @get(i), i
+    false
+
+  filter: ->
+    fn = fn.bind thisObject if thisObject
+
+    result = new @constructor
+
+    for i in [0...@length] by 1
+      val = @get i
+      result.push val if fn val, i
+
+    result
+
+  reduce: (fn, initialValue) ->
+    result = initialValue ? @get 0
+    result = fn result, @get(i), index, @ for i in [0...@length] by 1
+    result
+
+  reduceRight: ->
+    result = initialValue ? @get(@length - 1)
+    i = @length
+    result = fn result, @get(i - 1), i - 1, @ while i--
+    result

   _update: (prop, name) ->
     len = @_data.length


fix github account mapping.

fix github account mapping.

--- scripts/github-notify.coffee
+++ scripts/github-notify.coffee
@@ -84,12 +84,11 @@
       mentioned = mentioned.map (nick) -> nick[2].trim()
       mentioned = unique mentioned
       mentioned = mentioned.map (nick) ->
-        nick = nick.match(/@([\w\-\/]+)/)[1]
         for _uid, user of robot.brain.users
-          if user.githubLogin == nick
-            nick = user.name
+          if "@#{user.githubLogin}" == nick
+            nick = "@#{user.name}"
             break
-        "@#{nick}"
+        nick

       mentioned_line = "\nMentioned: #{mentioned.join(", ")}"
     else