fix addTag

fix addTag

--- both/methods/project/projectArray.coffee
+++ both/methods/project/projectArray.coffee
@@ -12,7 +12,11 @@
 			itemId = Random.id 8

 		addToSet = {}
-		addToSet[array] = _id: itemId, name: name
+
+		if array == 'tags'
+			addToSet[array] = _id: itemId, name: name, templates: []
+		else
+			addToSet[array] = _id: itemId, name: name

 		Projects.update projectId, $addToSet: addToSet


add some comments to wysiwyg js

add some comments to wysiwyg js

--- app/assets/javascripts/storytime/wysiwyg.js.coffee
+++ app/assets/javascripts/storytime/wysiwyg.js.coffee
@@ -42,6 +42,9 @@
       self.bindActionPanel $(this), mediumEditor

   setupMedium: ->
+    # Medium-editor keeps adding toolbars when this method gets triggered
+    # (i.e. when opening snippets modal on post edit page) and they all seem
+    # to get activated at the same time, so this clears out existing ones first
     $('.medium-editor-toolbar').remove()
     $('.medium-editor-anchor-preview').remove()
     mediumEditor = new MediumEditor('.medium-editor', mediumEditorOptions)
@@ -79,7 +82,7 @@
           $(this).text("HTML")
           mediumEditor.activate()
           $("#storytime-modal").removeClass("modal-wide")
-
+
     $("#storytime-modal").on 'hide.bs.modal', ->
       $(this).removeClass "modal-wide"


linting

linting

--- app/routes/index.coffee
+++ app/routes/index.coffee
@@ -3,7 +3,8 @@
 items = [{
   type: 'customer'
   title: 'Congressista'
-  description: 'Todos que participam de eventos, circuitos de palestras, congressos, encontros, etc'
+  description: 'Todos que participam de eventos, circuitos \
+                de palestras, congressos, encontros, etc'
 }, {
   type: 'customer'
   title: 'Organizador'
@@ -19,15 +20,16 @@
 }, {
   type: 'risk'
   title: 'Faltar de um dispositivo'
-  description: 'Vai que o cara não é muito tecnológico e não tem um celular/tablet moderninho?'
+  description: 'Vai que o cara não é muito tecnológico e \
+                não tem um celular/tablet moderninho?'
 }]

 hypothesis = [{
   customer: items[0]
   problem:  items[2]
   solution: items[3]
-  risk: items[4]
-  method: 'Interview'
+  risk:     items[4]
+  method:    'Interview'
   criterion: '10/20'
 }]


If auto-update is triggered by change then the change is reverted before the manifest is downloaded, the version id in manifest will match old version id. In this scenario the update callback should be called.

--- www/coffee/servers.coffee
+++ www/coffee/servers.coffee
@@ -226,7 +226,8 @@
 						servers[url].info = servers[url].oldInfo
 						servers[url].oldInfo = oldInfo
 					cb(status)
-
+			else
+				return cb {done: true}

 	getFileTransfer: ->
 		@fileTransfer ?= new FileTransfer()


Handling binary data and allowing to use string options

--- lib/beg.coffee
+++ lib/beg.coffee
@@ -2,10 +2,8 @@
 http  = require "http"
 https = require "https"

-request = (options, parseJson, callback) ->
-  [parseJson, callback] = [false, parseJson] unless callback?
-
-  if options.secure? and options.secure
+request = (options, {secure, parseJson, binary}, callback) ->
+  if (secure? and secure) or (/^https:\/\//.test options)
     client = https
   else
     client = http
@@ -15,11 +13,13 @@
       callback err

     response = ""
+    res.setEncoding("binary") if binary? and binary
     res.on "data", (chunk) ->
       response += chunk

     res.on "end", ->
       response = JSON.parse response if parseJson?
+
       callback null, response

   req.on "error", (err) ->


stopPinging quick fix

stopPinging quick fix

--- client/app/MainApp/account-mixins.coffee
+++ client/app/MainApp/account-mixins.coffee
@@ -53,13 +53,13 @@

       cycleChannel =->
         @off()
-        @stopPinging()
+        @stopPinging?()
         delete channels[@name]
         delete namesCache[@authenticationInfo.name]

       messageHandler =(kiteName, args) ->
         {method} = args
-        callback =
+        callback =
           if method is 'ready'
             ready.bind this
           else if method is 'error'
@@ -88,7 +88,7 @@
         namesCache[kiteName] = channelName
         return channelName

-      fetchChannel =(kiteName, callback)->
+      fetchChannel =(kiteName, callback)->
         channelName = getChannelName "kite-#{kiteName}"
         return callback readyChannels[channelName]  if readyChannels[channelName]
         channel = KD.remote.mq.subscribe channelName


use warn instead of console.warn

use warn instead of console.warn

--- client/landing/app/MainApp/routes.coffee
+++ client/landing/app/MainApp/routes.coffee
@@ -1,13 +1,12 @@
 do ->
   mainController = KD.getSingleton 'mainController'

-
   handleRoute =(groupId, route)->
-    console.log 'invoking a route by group id...'
+    log 'invoking a route by group id...'

   notFound =(route)->
     KDRouter.addRoute route, ->
-      console.warn "Contract warning: shared route #{route} is not implemented."
+      warn "Contract warning: shared route #{route} is not implemented."

   routes =


Update git.coffee

Update git.coffee

--- lib/git.coffee
+++ lib/git.coffee
@@ -1,9 +1,11 @@
-Git     = require 'promised-git'
+Git = require 'promised-git'

 getPath = ->
-  if atom.project.getRepo()
+  if atom.project?.getRepo()
     atom.project.getRepo().getWorkingDirectory()
+  else if atom.project
+    atom.project.getPath()
   else
-    atom.project.getPath()
+    __dirname

 module.exports = new Git(getPath())


Remove unnecessary character from QUERY_REGEX


Add ignorefile support to watch

Add ignorefile support to watch

--- lib/watch.iced
+++ lib/watch.iced
@@ -9,11 +9,14 @@
 request = require('request')
 mkdirp = require('mkdirp')
 sass = require('node-sass')
+parser = require('gitignore-parser')

 exports.run = (argv, done) ->

   await helpers.loadConfig(defer(err, config))
   if err? then done(err)
+
+  ignore = parser.compile(fs.readFileSync(config.ignore_file, 'utf8'))

   await helpers.getTarget(config, defer(err, target))
   if err? then done(err)
@@ -31,7 +34,13 @@
   watcher.on('all', (event, filepath) ->
     extension = path.extname(filepath).substr(1)

+    # Ignore quickshot.json
     if filepath.match(/^quickshot.json$/) then return
+    # Ignore hidden files
+    if filepath.match(/^\..*$/) then return
+
+    # Ignore paths configured in ignore file
+    if ignore.denies(filepath) then return

     switch event
       when 'add', 'change'


bug fix: project settings page not loading due to wrong shouldComponentUpdate

--- salvus/page/project_settings.cjsx
+++ salvus/page/project_settings.cjsx
@@ -645,9 +645,7 @@
         project_id  : rtypes.string.isRequired

     shouldComponentUpdate: (next) ->
-        if not @props.user_map? or not @props.project_map? or not next.user_map? or not next.project_map?
-            return false
-        return not immutable.is(@props.project_map.get(@props.project_id), next.project_map.get(@props.project_id))
+        return @props.project_map?.get(@props.project_id) != next.project_map?.get(@props.project_id)

     render: ->
         project = @props.project_map?.get(@props.project_id)


Fixed #329 refreshing grid filters when a value is removed

--- wzk/ui/grid/Filter.coffee
+++ wzk/ui/grid/Filter.coffee
@@ -50,7 +50,7 @@
   ###
   getName: ->
     unless @name
-      @name = @el.name.split('__').pop()
+      @name = @getFilter().split('__').shift()
     @name

   ###*
@@ -69,7 +69,7 @@
     @return {boolean}
   ###
   apply: (query) ->
-    changed = query.isChanged @getName(), @getValue()
+    changed = query.isChanged @getFilter(), @getValue()
     if changed
       query.filter @getFilter(), @getValue()
     changed


readme test use correct function sig

readme test use correct function sig

--- test/functional/readme.coffee
+++ test/functional/readme.coffee
@@ -11,8 +11,8 @@
     it 'illustrates basic usage', ->
       hello = llvm.CodeUnit "hello"

-      main = hello.makeFunction "main", llvm.getFunctionTy llvm.type.i32
-      puts = hello.declareFunction "puts", llvm.getFunctionTy llvm.type.i32, llvm.type.pointerTo llvm.type.i8
+      main = hello.makeFunction "main", llvm.type.i32
+      puts = hello.declareFunction "puts", llvm.type.i32, llvm.type.pointerTo llvm.type.i8

       text = hello.constant "Hello, world!\n"


Update comment

Update comment

--- test/lib/RegistrarSpec.coffee
+++ test/lib/RegistrarSpec.coffee
@@ -17,7 +17,7 @@

     beforeEach ->

-    # A domain can't be deleted, so this test won't ever pass again in record mode
+    # a domain can't be deleted, so this test won't ever pass again in record mode
     it "should register `TestDomain` domain if it's not registered", ->
       new Promise (resolve, reject) ->
         nock.back "test/fixtures/registrar/RegisterTestDomainIfNotRegistered.json", (recordingDone) ->
@@ -26,6 +26,7 @@
           .catch reject
           .finally recordingDone

+    # you might have already registered some other domains, so if you re-record the fixture, it'll be different (and I think it's OK)
     it "should skip `TestDomain` domain if it's already registered", ->
       new Promise (resolve, reject) ->
         nock.back "test/fixtures/registrar/SkipTestDomainIfAlreadyRegistered.json", (recordingDone) ->


Updates to memberAssignments tests.

Updates to memberAssignments tests.

--- test/memberAssignments.coffee
+++ test/memberAssignments.coffee
@@ -1,16 +1,84 @@
-memberAssignment = {}
+describe 'Member Assignments', ->

-describe 'memberAssignments', ->
+  member = null
+  location = null
+  event = null
+  assignment = null
+  memberAssignment = null

+  # Create a member
   before (done) ->
-    memberAssignment = teamsnap.createMemberAssignment()
-    memberAssignment.memberId = 2
-    memberAssignment.assignmentId = 3
-    memberAssignment.teamId = team.id
-    done()
+    member = teamsnap.createMember()
+    member.firstName = 'Esdee'
+    member.lastName = 'Kay'
+    member.teamId = team.id
+    teamsnap.saveMember member, (err, result) ->
+      expect(err).to.be.null
+      result.should.have.property('type', 'member')
+      done()
+
+  # Create a location
+  before (done) ->
+    location = teamsnap.createLocation()
+    location.name = 'Test Statistic Location'
+    location.teamId = team.id
+    teamsnap.saveLocation location, (err, result) ->
+      expect(err).to.be.null
+      result.should.have.property('type', 'location')
+      done()
+
+  # Create an event
+  before (done) ->
+    event = teamsnap.createEvent()
+    event.name = 'Test Statistic Event'
+    event.teamId = team.id
+    event.locationId = location.id
+    event.startDate = new Date()
+    teamsnap.saveEvent event, (err, result) ->
+      expect(err).to.be.null
+      result.should.have.property('type', 'event')
+      done()
+
+  # Create an assignment
+  before (done) ->
+    assignment = teamsnap.createAssignment()
+    assignment.description = 'Test Assignment'
+    assignment.eventId = event.id
+    teamsnap.saveAssignment assignment, (err, result) ->
+      expect(err).to.be.null
+      result.should.have.property('type', 'assignment')
+      done()
+
+  # Delete assignment
+  after (done) ->
+    teamsnap.deleteAssignment assignment, (err, result) ->
+      expect(err).to.be.null
+      done()
+
+  # Delete event
+  after (done) ->
+    teamsnap.deleteEvent event, (err, result) ->
+      expect(err).to.be.null
+      done()
+
+  # Delete location
+  after (done) ->
+    teamsnap.deleteLocation location, (err, result) ->
+      expect(err).to.be.null
+      done()
+
+  # Delete member
+  after (done) ->
+    teamsnap.member member, (err, result) ->
+      expect(err).to.be.null
+      done()

   after (done) ->
-    memberAssignment = {}
+    member = null
+    location = null
+    event = null
+    assignment = null
+    memberAssignment = null
     done()

   describe 'teamsnap.loadMemberAssignments', ->
@@ -19,10 +87,14 @@
         result.should.be.an('array')
         done()

-  describe 'teamsnap.createMemberAssignment', ->
+  describe 'teamsnap.createMemberAssignment', (done) ->
     it 'should create an item with type "memberAssignment"', ->
       memberAssignment = teamsnap.createMemberAssignment()
       memberAssignment.should.have.property 'type', 'memberAssignment'
+      memberAssignment.memberId = member.id
+      memberAssignment.assignmentId = assignment.id
+      memberAssignment.teamId = team.id
+      done()

   describe 'teamsnap.saveMemberAssignment', ->
     it 'should save a member assignment"', (done) ->


Make unit test resolveConflict less breakable

--- test/unit/remote/index.coffee
+++ test/unit/remote/index.coffee
@@ -681,8 +681,8 @@
                 path: 'cat-conflict-2015-12-01T01:02:03Z.jpg'
                 docType: 'file'
                 checksum: checksum
-                creationDate: new Date()
-                lastModification: new Date()
+                creationDate: new Date().toISOString()
+                lastModification: new Date().toISOString()
                 size: 36901
             remoteDoc = @remote.createRemoteDoc src, binary: binary
             @couch.put remoteDoc, (err, created) =>
@@ -701,7 +701,7 @@
                             path: ''
                             name: dst.path
                             docType: 'file'
-                            lastModification: dst.lastModification.toISOString()
+                            lastModification: dst.lastModification
                             size: 36901
                             binary:
                                 file:


[Server] Remove  unused variables

[Server] Remove  unused variables

--- workers/social/lib/social/render/loggedout/subpage.coffee
+++ workers/social/lib/social/render/loggedout/subpage.coffee
@@ -20,13 +20,11 @@

 module.exports = (options, callback)->

-  {account, name, section, models, bongoModels, client} = options
+  {name, section, models, bongoModels, client} = options

   getStyles    = require './../styleblock'
   fetchScripts = require './../scriptblock'
   model        = models.first if models and Array.isArray models
-  isLoggedIn   = account.type is "registered"
-

   prepareHTML  = (scripts)->
     """
@@ -64,4 +62,4 @@
     """

   fetchScripts {bongoModels, client}, (err, scripts)->
-    callback null, prepareHTML scripts+    callback null, prepareHTML scripts


Add Hungarian

Add Hungarian

--- app/index.coffee
+++ app/index.coffee
@@ -47,6 +47,7 @@
     zh_cn: label: "简体中文 Chinese (Simplified)", strings: './translations/zh-cn.json'
     zh_tw: label: "繁體中文 Chinese (Traditional)", strings: './translations/zh-tw.json'
     de: label: "Deutsch", strings: "./translations/de.json"
+    hu: label: "Magyar", strings: "./translations/hu.json"

 languageManager.on 'change-language', (e, code, strings) ->
   translate.load strings


ain't nobody got time for that better regex

--- scripts/nobody-got-time.coffee
+++ scripts/nobody-got-time.coffee
@@ -1,7 +1,7 @@
 # Ain't nobody got time for that

 module.exports = (robot) ->
-  robot.hear /nobody got time for that/i, (msg) ->
+  robot.hear /(don't|ain't nobody) (have|got) time( for that)?/i, (msg) ->
     msg.send "http://mlkshk.com/r/HMCU.gif"


replace custom projection with proj4

replace custom projection with proj4

--- bokehjs/src/coffee/renderer/tile/tile_utils.coffee
+++ bokehjs/src/coffee/renderer/tile/tile_utils.coffee
@@ -1,3 +1,6 @@
+proj4 = require "proj4"
+mercator = proj4.defs('GOOGLE')
+wgs84 = proj4.defs('WGS84')

 class ProjectionUtils

@@ -5,16 +8,10 @@
     @origin_shift = 2 * Math.PI * 6378137 / 2.0

   geographic_to_meters: (xLon, yLat) ->
-    mx = xLon * @origin_shift / 180.0
-    my = Math.log( Math.tan((90 + yLat) * Math.PI / 360.0 )) / (Math.PI / 180.0)
-    my = my * @origin_shift / 180.0
-    return [mx, my]
+    return  proj4(wgs84, mercator, [xLon, yLat])

   meters_to_geographic: (mx, my) ->
-    lon = (mx / @origin_shift) * 180.0
-    lat = (my / @origin_shift) * 180.0
-    lat = 180 / Math.PI * (2 * Math.atan(Math.exp(lat * Math.PI / 180.0)) - Math.PI / 2.0)
-    return [lon, lat]
+    return  proj4(mercator, wgs84, [mx, my])

   geographic_extent_to_meters: (extent) ->
     [xmin, ymin, xmax, ymax] = extent


closes #22: check for when there's no token found

--- server/code/model/token.coffee
+++ server/code/model/token.coffee
@@ -17,6 +17,8 @@
     @dbClass.findOne {token: token}, (err, token) ->
       if err?
         callback err, null
+      else if not token?
+        callback 'Not found', null
       else
         newToken = new Token
         _.extend newToken, token.toObject()


kite login type changed

kite login type changed

--- server/lib/server/index.coffee
+++ server/lib/server/index.coffee
@@ -120,7 +120,9 @@

   res.header "Content-Type", "application/json"

-  {username, key, name} = req.query
+  {owner, key, name} = req.query
+
+  username = owner

   unless username and key and name
     res.send


removed console.log

removed console.log

--- server/lib/server/index.coffee
+++ server/lib/server/index.coffee
@@ -115,7 +115,7 @@
     if err? or !kite?
       res.send 401
       return
-    console.log kite.kiteName
+
     rabbitAPI.newUser req.query.key, req.query.secret, kite.kiteName, (err, data) =>
       creds =
         protocol  : 'amqp'


Simplify check

Simplify check

--- resources/assets/coffee/react/_components/beatmap-icon.coffee
+++ resources/assets/coffee/react/_components/beatmap-icon.coffee
@@ -22,14 +22,8 @@
 @BeatmapIcon = (props) ->
   beatmap = props.beatmap

-  difficultyRating =
-    if props.overrideVersion?
-      props.overrideVersion
-    else
-      BeatmapHelper.getDiffRating beatmap.difficulty_rating
-
+  difficultyRating = props.overrideVersion ? BeatmapHelper.getDiffRating(beatmap.difficulty_rating)
   showTitle = (props.showTitle ? true) && !props.overrideVersion?
-
   mode = if beatmap.convert then 'osu' else beatmap.mode

   div


Default not needed

Default not needed

--- resources/assets/coffee/react/_components/block-button.coffee
+++ resources/assets/coffee/react/_components/block-button.coffee
@@ -22,9 +22,6 @@
 bn = 'textual-button'

 class @BlockButton extends React.PureComponent
-  @defaultProps = modifiers: []
-
-
   constructor: (props) ->
     super props


update tests to use safe_exec

update tests to use safe_exec

--- services/filestore/test/unit/coffee/FileConverterTests.coffee
+++ services/filestore/test/unit/coffee/FileConverterTests.coffee
@@ -10,10 +10,9 @@

 	beforeEach ->

-		@child_process =
-			exec : sinon.stub()
+		@safe_exec = sinon.stub()
 		@converter = SandboxedModule.require modulePath, requires:
-			'child_process': @child_process
+			"./SafeExec": @safe_exec
 			"logger-sharelatex":
 				log:->
 				err:->
@@ -25,43 +24,43 @@
 	describe "convert", ->

 		it "should convert the source to the requested format", (done)->
-			@child_process.exec.callsArgWith(2)
+			@safe_exec.callsArgWith(2)
 			@converter.convert @sourcePath, @format, (err)=>
-				args = @child_process.exec.args[0][0]
+				args = @safe_exec.args[0][0]
 				args.indexOf(@sourcePath).should.not.equal -1
 				args.indexOf(@format).should.not.equal -1
 				done()

 		it "should return the dest path", (done)->
-			@child_process.exec.callsArgWith(2)
+			@safe_exec.callsArgWith(2)
 			@converter.convert @sourcePath, @format, (err, destPath)=>
 				destPath.should.equal "#{@sourcePath}.#{@format}"
 				done()

 		it "should return the error from convert", (done)->
-			@child_process.exec.callsArgWith(2, @error)
+			@safe_exec.callsArgWith(2, @error)
 			@converter.convert @sourcePath, @format, (err)=>
 				err.should.equal @error
 				done()

 		it "should not accapt an non aproved format", (done)->
-			@child_process.exec.callsArgWith(2)
+			@safe_exec.callsArgWith(2)
 			@converter.convert @sourcePath, "ahhhhh", (err)=>
 				expect(err).to.exist
 				done()

 	describe "thumbnail", ->
 		it "should call converter resize with args", (done)->
-			@child_process.exec.callsArgWith(2)
+			@safe_exec.callsArgWith(2)
 			@converter.thumbnail @sourcePath, (err)=>
-				args = @child_process.exec.args[0][0]
+				args = @safe_exec.args[0][0]
 				args.indexOf(@sourcePath).should.not.equal -1
 				done()

 	describe "preview", ->
 		it "should call converter resize with args", (done)->
-			@child_process.exec.callsArgWith(2)
+			@safe_exec.callsArgWith(2)
 			@converter.preview @sourcePath, (err)=>
-				args = @child_process.exec.args[0][0]
+				args = @safe_exec.args[0][0]
 				args.indexOf(@sourcePath).should.not.equal -1
 				done()


Test creating, listing and revoking invites as owner

--- services/web/test/acceptance/coffee/ProjectInviteTests.coffee
+++ services/web/test/acceptance/coffee/ProjectInviteTests.coffee
@@ -87,6 +87,14 @@
 				redir: redir
 		}, callback

+tryGetInviteList = (user, projectId, callback=(err, response, body)->) ->
+	user.getCsrfToken (error) =>
+		return callback(error) if error?
+		user.request.get {
+			url: "/project/#{projectId}/invites"
+			json: true
+		}, callback
+

 # Expectations
 expectProjectAccess = (user, projectId, callback=(err,result)->) ->
@@ -167,6 +175,14 @@
 		expect(err).to.be.oneOf [null, undefined]
 		expect(response.statusCode).to.equal 302
 		expect(response.headers.location).to.equal "/project/#{invite.projectId}"
+		callback()
+
+expectInviteListCount = (user, projectId, count, callback=(err)->) ->
+	tryGetInviteList user, projectId, (err, response, body) ->
+		expect(err).to.be.oneOf [null, undefined]
+		expect(response.statusCode).to.equal 200
+		expect(body).to.have.all.keys ['invites']
+		expect(body.invites.length).to.equal count
 		callback()

@@ -182,6 +198,56 @@
 			(cb) => @user.logout cb
 			(cb) => @sendingUser.login cb
 		], done
+
+	describe 'creating invites', ->
+
+		beforeEach (done) ->
+			@projectName = "wat"
+			@projectId = null
+			@fakeProject = null
+			done()
+
+		afterEach ->
+
+		describe 'creating two invites', ->
+
+			beforeEach (done) ->
+				Async.series [
+					(cb) =>
+						createProject @sendingUser, @projectName, (err, projectId, project) =>
+							@projectId = projectId
+							@fakeProject = project
+							cb()
+				], done
+
+			afterEach (done) ->
+				Async.series [
+					(cb) => @sendingUser.deleteProject(@projectId, cb)
+					(cb) => @sendingUser.deleteProject(@projectId, cb)
+				], done
+
+			it 'should allow the project owner to create and remove invites', (done) ->
+				@invite = null
+				Async.series [
+					(cb) => expectProjectAccess @sendingUser, @projectId, cb
+					(cb) => expectInviteListCount @sendingUser, @projectId, 0, cb
+					# create invite, check invite list count
+					(cb) => createInvite @sendingUser, @projectId, @email, (err, invite) =>
+						return cb(err) if err
+						@invite = invite
+						cb()
+					(cb) => expectInviteListCount @sendingUser, @projectId, 1, cb
+					(cb) => revokeInvite @sendingUser, @projectId, @invite._id, cb
+					(cb) => expectInviteListCount @sendingUser, @projectId, 0, cb
+					# and a second time
+					(cb) => createInvite @sendingUser, @projectId, @email, (err, invite) =>
+						return cb(err) if err
+						@invite = invite
+						cb()
+					(cb) => expectInviteListCount @sendingUser, @projectId, 1, cb
+					(cb) => revokeInvite @sendingUser, @projectId, @invite._id, cb
+					(cb) => expectInviteListCount @sendingUser, @projectId, 0, cb
+				], done

 	describe 'clicking the invite link', ->


No need to create a new function for each call of setValue, kudos goes to syasar

--- client/Framework/components/inputs/KDMultipleChoice.coffee
+++ client/Framework/components/inputs/KDMultipleChoice.coffee
@@ -53,28 +53,27 @@

   getValue:-> @currentValue

+  setCurrent = (view, label)=>
+    if label in @currentValue
+      view.$("a[name$='#{label}']").removeClass('active')
+      view.currentValue.splice(view.currentValue.indexOf(label), 1)
+    else
+      view.$("a[name$='#{label}']").addClass('active')
+      view.currentValue.push label
+
   setValue:(label, wCallback = yes)->
     {multiple} = do @getOptions

     if multiple
-
+      # FIXME later with .first
       @oldValue = [obj for obj in @currentValue][0]

-      setCurrent = (label)=>
-        if label in @currentValue
-          @$("a[name$='#{label}']").removeClass('active')
-          @currentValue.splice(@currentValue.indexOf(label), 1)
-        else
-          @$("a[name$='#{label}']").addClass('active')
-          @currentValue.push label
-
       if Array.isArray label
-        [setCurrent(val) for val in label]
+        [setCurrent(@, val) for val in label]
       else
-        setCurrent label
+        setCurrent @, label

       do @switchStateChanged if wCallback
-
     else
       @$("a").removeClass('active')
       @$("a[name$='#{label}']").addClass('active')


Disable select previous node when node removed feature for now

--- client/Framework/components/tree/treeviewcontroller.coffee
+++ client/Framework/components/tree/treeviewcontroller.coffee
@@ -321,7 +321,8 @@

     if nodeData in @indexedNodes
       index = @indexedNodes.indexOf nodeData
-      @selectNode @nodes[@getNodeId @indexedNodes[index-1]] if index-1 >= 0
+      # Disable this for now, useless for most cases, FIXME GG
+      # @selectNode @nodes[@getNodeId @indexedNodes[index-1]] if index-1 >= 0
       @indexedNodes.splice index, 1
       # todo: make decoration with events
       if @nodes[@getNodePId nodeData] and not \


more sound on project view

more sound on project view

--- project/coffee/view/components/ObjectsList.coffee
+++ project/coffee/view/components/ObjectsList.coffee
@@ -24,6 +24,7 @@
         null

     beforeChange : (event, slick, currentSlide, nextSlide) =>
+        Backbone.trigger( 'SoundController:play', 'touchable' )
         @B().objectsContentHackOrder = nextSlide
         @$el.find('video').each ->
             @.pause()


[ADD] Re-add 'spaces.show' as external route to builder-rat config

--- src/thinkspace/client/thinkspace-builder/addon/_config.coffee
+++ src/thinkspace/client/thinkspace-builder/addon/_config.coffee
@@ -6,7 +6,7 @@

   engine:
     mount: 'builder'
-    external_routes: [{login: 'users.sign_in'}, 'pe.details', 'rat.details']
+    external_routes: [{login: 'users.sign_in'}, 'pe.details', 'rat.details', 'spaces.show']

   ns:
     namespaces:


复制后写入项目配置文件并打开

复制后写入项目配置文件并打开

--- lib/project/create-project.coffee
+++ lib/project/create-project.coffee
@@ -98,6 +98,16 @@
               throw err if err
               console.log 'deleted!'
             Util.delete gfp,delSuccess
+
+            appConfigPath = pathM.join info.appPath,desc.ProjectConfigFileName
+            writeCB = (err) =>
+              throw err if err
+              atom.workspace.open appConfigPath
+              aft = =>
+                Util.rumAtomCommand('tree-view:reveal-active-file')
+              _.debounce(aft,300)
+            Util.writeJson appConfigPath, Util.formatAppConfigToObj(info), writeCB
+
             @modalPanel.item.children(".loading-mask").remove()
             atom.project.addPath(info.appPath)
             Util.rumAtomCommand 'tree-view:toggle' if ChameleonBox.$('.tree-view-resizer').length is 0


Record route in transistion store when it changes

--- tutor/src/components/app.cjsx
+++ tutor/src/components/app.cjsx
@@ -9,6 +9,11 @@
 {SpyMode} = require 'shared'
 {CourseStore} = require '../flux/course'
 {TransitionActions, TransitionStore} = require '../flux/transition'
+{ LocationSubscriber } = require 'react-router/Broadcasts'
+
+RouteChange = (props) ->
+  TransitionActions.load(props.pathname)
+

 module.exports = React.createClass
   displayName: 'App'
@@ -44,6 +49,8 @@
     })

+      <LocationSubscriber>{RouteChange}</LocationSubscriber>
+
       <SpyMode.Wrapper>
         <Navbar {...@props}/>
         <RoutingHelper.component routes={Router.getRenderableRoutes(renderers)} />


cleaning up

cleaning up

--- components/auction_reminder/index.coffee
+++ components/auction_reminder/index.coffee
@@ -20,7 +20,7 @@
     if window.location.pathname == @auction.href()
       return
     # Reminder only shows if 24 hours until end
-    if moment(@auction.get('end_at')).diff(moment(),'hours') > 230
+    if moment(@auction.get('end_at')).diff(moment(),'hours') > 23
       return

     @$container = $('body')
@@ -37,16 +37,11 @@
     @setupClock()
     @$container.append @$el

-    console.log Cookies.get('firstAuctionReminderSeen')
-
     if Cookies.get('firstAuctionReminderSeen')
-      console.log "seen"
       @$('.modal-dialog').css
         bottom: "40px"
     else
-      console.log "unseen"
       Cookies.set('firstAuctionReminderSeen', true)
-      #Activate after 5 seconds
       activate = => @$dialog.addClass("is-spring-in")
       _.delay(activate,5000)


payment: fix login form reference

payment: fix login form reference

--- client/Main/payment/existingaccountworkflow.coffee
+++ client/Main/payment/existingaccountworkflow.coffee
@@ -6,7 +6,7 @@
       callback : (credentials) =>
         KD.getSingleton('mainController').handleLogin credentials, (err) =>
           @loginForm.button.hideLoader()
-          if (KD.showError err) and err?.field of loginForm
+          if (KD.showError err) and err?.field of @loginForm
               @loginForm[err.field].decorateValidation err
           else
             @emit "DataCollected",


activity: get rid of unncessary spans

activity: get rid of unncessary spans

--- client/activity/lib/util/prepareThreadTitle.coffee
+++ client/activity/lib/util/prepareThreadTitle.coffee
@@ -37,8 +37,7 @@
   else
     children = makeProfileText sample.get 0

-  return \
-    {children}
+  return children

 helper =

@@ -56,5 +55,4 @@
   getPlusMorePartial: (participantCount, nameCount) ->
     text = " #{participantCount - nameCount - 1} more"

-    return \
-      {text}
+    return text


Use length based sort to get shortest domain

--- client/app/MainApp/VirtualizationController.coffee
+++ client/app/MainApp/VirtualizationController.coffee
@@ -161,7 +161,7 @@
     return callback null, domains  if domains
     KD.remote.api.JVM.fetchDomains vmName, (err, domains=[])=>
       return callback err, domains  if err
-      callback null, @vmDomains[vmName] = domains.sort()
+      callback null, @vmDomains[vmName] = domains.sort (x, y)-> x.length>y.length

   resetVMData:->
     @vms = @groupVms = []


Minor codestyling for VideoCollaborationController

--- client/ide/lib/videocollaborationcontroller.coffee
+++ client/ide/lib/videocollaborationcontroller.coffee
@@ -54,19 +54,13 @@
     callback @videoModel.getParticipants()

-  startVideoCollaboration: ->
-
-    @videoModel.start()
+  startVideoCollaboration: -> @videoModel.start()

-  endVideoCollaboration: ->
-
-    @videoModel.end()
+  endVideoCollaboration: -> @videoModel.end()

-  muteParticipant: (nickname) ->
-
-    @videoModel.muteParticipant nickname
+  muteParticipant: (nickname) -> @videoModel.muteParticipant nickname

   toggleVideoControl: (type, activeState) ->


IDEStatusBar: Set yes or no to progress bar

--- client/ide/lib/views/statusbar/idestatusbar.coffee
+++ client/ide/lib/views/statusbar/idestatusbar.coffee
@@ -135,6 +135,7 @@

   startSession: ->

+    @share.getOptions().startProgress = yes
     @share.updateProgress 0 # Make sure initial value is 0

     PROGRESS_DELAYS.forEach (item) =>
@@ -298,6 +299,7 @@

   handleSessionEnd: ->

+    @share.getOptions().startProgress = no
     ide = kd.singletons.appManager.getFrontApp()

     if @amIHost_()


fix layout bug

fix layout bug

--- @client/profile_menu.coffee
+++ @client/profile_menu.coffee
@@ -81,7 +81,6 @@
                     color: if menu_showing then '#777'
                     fontSize: 18
                     position: 'relative'
-                    top: -4
                     paddingRight: 12
                   current_user.name
                 I
@@ -104,6 +103,8 @@
               borderRadius: 8
               padding: '3px 4px'
               fontWeight: 600
+              display: 'flex'
+              alignItems: 'center'

             anchor_when_open_style:
               backgroundColor: 'transparent'


Added SSL Config Options.

Added SSL Config Options.

--- server/common/dal/mongo.coffee
+++ server/common/dal/mongo.coffee
@@ -6,15 +6,25 @@

 getConnectionString = (conf) ->
   uri = "mongodb://"
+
+  if conf.username?
+    uri += conf.username + ":" + conf.password + "@"
+
   separator = ""
   if conf.servers?
     _.each conf.servers, (server) ->
       uri += separator +  server.host + ":" + server.port
       separator = ","
-    uri += "/" + conf.name
+  else
+    uri += conf.host + ":" + conf.port

+  uri += "/" + conf.name
+  if conf.ssl
+    uri += "?ssl=true"
   else
-    uri += conf.host + ":" + conf.port + "/" + conf.name
+    uri += "?ssl=false"
+  if conf.authSource?
+    uri += "&authSource=" + conf.authSource
   uri

 schemaFactory = (def) ->
@@ -35,11 +45,9 @@
   connect: (conf, cb) ->
     port = parseInt conf.port

-    opts =
-      user: conf.username
-      pass: conf.password
     uri = getConnectionString(conf)
-    mongoose.connect uri, opts
+
+    mongoose.connect uri, {}

     mongoose.connection.on 'connected',  () ->
       cb() if cb


image proxy was not responding for most images, this change made it work

--- server/lib/server/index.coffee
+++ server/lib/server/index.coffee
@@ -98,7 +98,7 @@

 app.get "/-/imageProxy", (req, res)->
   if req.query.url
-    req.pipe(request(req.query.url)).pipe(res)
+    request(req.query.url).pipe(res)
   else
     res.send 404


[LoadingFree] use bongo models

[LoadingFree] use bongo models

--- server/lib/server/index.coffee
+++ server/lib/server/index.coffee
@@ -356,7 +356,9 @@
   if slug = req.query._escaped_fragment_
     return Crawler.crawl koding, req, res, slug
   else
+
     {JGroup} = koding.models
+    bongoModels = koding.models

     generateFakeClient req, res, (err, client)->
       if err or not client


Fixes datetime missing event for interventions form

--- app/assets/javascripts/backend/interventions.js.coffee
+++ app/assets/javascripts/backend/interventions.js.coffee
@@ -179,7 +179,8 @@
     $(this).each ->
       E.interventions.refresh $(this)

-  $(document).on "keyup change", ".nested-fields.working-period:first-child input.intervention-started-at", ->
+  $(document).on "keyup change dp.change", ".nested-fields.working-period:first-child input.intervention-started-at", (e) ->
+    console.log e
     $(this).each ->
       E.interventions.updateAvailabilityInstant($(this).val())


Enable intervention form create button

Enable intervention form create button

--- app/assets/javascripts/backend/interventions.js.coffee
+++ app/assets/javascripts/backend/interventions.js.coffee
@@ -168,13 +168,11 @@
           data: form.serialize()
           beforeSend: ->
             computing.prop 'state', 'waiting'
-            $("#new_intervention").submit(false)
             $('.form-actions .primary').attr("disabled", true)
           error: (request, status, error) ->
             computing.prop 'state', 'ready'
             false
           success: (data, status, request) ->
-            $("#new_intervention").submit(null)
             $('.form-actions .primary').attr("disabled", null)
             console.group('Unserialize intervention updated by ' + updaterId)
             # Updates elements with new values


Allow periodic checks while scrolling

Allow periodic checks while scrolling

The previous implementation would wait until scrolling stopped to check
and potentially load the next page results. This change allows for
checks to occur (periodically, at the scrollDelay frequency) so that
results can be loaded while scrolling is still happening.

--- app/assets/javascripts/jquery.infinite-pages.js.coffee
+++ app/assets/javascripts/jquery.infinite-pages.js.coffee
@@ -51,14 +51,23 @@
     init: ->

       # Debounce scroll event to improve performance
+      scrollDelay = 250
       scrollTimeout = null
-      scrollHandler = (=> @check())
+      lastCheckAt = null
+      scrollHandler = (=>
+        lastCheckAt = +new Date
+        @check()
+      )
+
+      # Have we waited enough time since the last check?
+      shouldCheck = -> +new Date > lastCheckAt + scrollDelay

       @$context.scroll ->
+        scrollHandler() if shouldCheck # Call the check once every scrollDelay ms
         if scrollTimeout
           clearTimeout(scrollTimeout)
           scrollTimeout = null
-        scrollTimeout = setTimeout(scrollHandler, 250)
+        scrollTimeout = setTimeout(scrollHandler, scrollDelay)

       # Set a data attribute so we can find again after a turbolink page is loaded
       @$container.attr('data-jquery-infinite-pages-container', @instanceId)


Refactor session service, add isAdmin() to check for admin status of current authenticated user

--- app/assets/javascripts/services/session_service.coffee
+++ app/assets/javascripts/services/session_service.coffee
@@ -4,11 +4,11 @@
   '$window'
   'UserService'
   ($http, $q, $window, User)->
-    currentUser = null
+    authenticatedUser = null

     equal = angular.equals

-    setUser = (user)-> currentUser = user
+    setUser = (user)-> authenticatedUser = user

     checkStatus = -> User.find('me').then(setUser)

@@ -17,21 +17,19 @@

     signOut = ->
       $http.delete('/user/sign_out').then ->
-        currentUser = null
+        authenticatedUser = null
         #hacky way of redirecting since base-tag supported routing will hijack this via $location
         $window.location.href = '/user/sign_in'

+    isCurrentUser = (user)-> equal(authenticatedUser, user)
+
+    currentUser = (ensurePromise=true)->
+      if ensurePromise then $q.when(authenticatedUser) else authenticatedUser
+
+    isAdmin = -> authenticatedUser.admin
+
     #load the current user if signed in and set to promise that will be resolved and watched automatically
-    currentUser = checkStatus()
+    authenticatedUser = checkStatus()

-    isCurrentUser = (user)-> equal(currentUser, user)
-
-    {
-      checkStatus: checkStatus
-      signIn: signIn
-      signOut: signOut
-      currentUser: (ensurePromise=true)->
-        if ensurePromise then $q.when(currentUser) else currentUser
-      isCurrentUser: isCurrentUser
-    }
+    {checkStatus, signIn, signOut, isCurrentUser, currentUser, isAdmin}
 ])


intermediate checkin, got radio buttons working along with text and select

--- app/assets/javascripts/hanuman/conditional-logic.js.coffee
+++ app/assets/javascripts/hanuman/conditional-logic.js.coffee
@@ -8,13 +8,22 @@
       $ruleElement = $(this)
       if $ruleElement.attr('data-rule').length > 0
         rule = $.parseJSON($ruleElement.attr("data-rule")).rule
-        console.log rule
         $(rule.conditions).each ->
-          questionId = this.question_id
+          condition = this
+          questionId = condition.question_id
           $triggerContainer = $("[data-question-id=" + questionId + "]")
-          $triggerElement = $triggerContainer.find('.form-control')
-          self.setDefaultState(rule.question_id, $triggerElement, this.operator, this.answer)
-          self.bindConditions(rule.question_id, $triggerElement, this.operator, this.answer)
+          $triggerElement = $triggerContainer.find(".form-control")
+          # text, textarea, select
+          if $triggerElement.length < 2
+            self.setDefaultState(rule.question_id, $triggerElement, condition.operator, condition.answer)
+            self.bindConditions(rule.question_id, $triggerElement, condition.operator, condition.answer)
+          # radio buttons
+          else
+            for element in $triggerElement
+              do (element) ->
+                self.setDefaultState(rule.question_id, $(element), condition.operator, condition.answer)
+                self.bindConditions(rule.question_id, $(element), condition.operator, condition.answer)
+
     return

   # set the default hide show conditions
@@ -38,7 +47,7 @@

   #clear questions
   clearQuestions: (container) ->
-    container.find("input[type!=hidden]").val("")
+    # container.find("input[type!=hidden]").val("")

   #evaluate conditional logic rules
   evaluateRules: (operator, answer, value) ->
@@ -66,6 +75,16 @@

   # get value of triggering question
   getValue: ($conditionElement) ->
+    if $conditionElement.is(":radio")
+      if $conditionElement.is(":checked")
+        return $conditionElement.val()
+      else
+        return
+    if $conditionElement.is(":checkbox")
+      if $conditionElement.is(":checked")
+        return $conditionElement.val()
+      else
+        return
     $conditionElement.val()

 $ ->


Make automatic primary concepts editable in Level editor

A restriction on concepts was being erroneously applied to
primary concepts, which all need to be editable.

--- app/views/editor/level/settings/SettingsTabView.coffee
+++ app/views/editor/level/settings/SettingsTabView.coffee
@@ -157,7 +157,7 @@
     super options

   onClick: (e) ->
-    return if @$el.hasClass('concept-automatic')  # Don't allow editing of automatic concepts
+    return if this.parent.keyForParent is 'concepts' and @$el.hasClass('concept-automatic')  # Don't allow editing of automatic concepts
     super e

 class ConceptsListNode extends TreemaNode.nodeMap.array


change alert close method

change alert close method

--- JSKit/03_JSAlertView.coffee
+++ JSKit/03_JSAlertView.coffee
@@ -54,8 +54,6 @@
                             @delegate.clickedButtonAtIndex(text, @_self)
                         when "JSAlertViewStyleDefault"
                             @delegate.clickedButtonAtIndex(1, @_self)
-                if (@delegate? && typeof @delegate.closedDialog == "function")
-                    @delegate.closedDialog(@_self)
                 $(@_viewSelector+"_form").dialog("close")
                 @_self.removeFromSuperview()
         if (@cancel == true)
@@ -63,8 +61,6 @@
                 Cancel:=>
                     if (@delegate? && typeof @delegate.clickedButtonAtIndex == "function")
                         @delegate.clickedButtonAtIndex(0, @_self)
-                    if (@delegate? && typeof @delegate.closedDialog == "function")
-                        @delegate.closedDialog(@_self)
                     $(@_viewSelector+"_form").dialog("close")
                     @_self.removeFromSuperview()
             buttonhash['Cancel'] = cancelmethod['Cancel']
@@ -94,6 +90,8 @@
         if (@_closeEvent?)
             $(@_viewSelector+"_form").dialog
                 close: =>
+                    if (@delegate? && typeof @delegate.closedDialog == "function")
+                        @delegate.closedDialog(@_self)
                     @_closeEvent(@_self)
         $(@_viewSelector+"_form").dialog("open")


removing console.log

removing console.log

--- src/scripts/modules/media/header/header.coffee
+++ src/scripts/modules/media/header/header.coffee
@@ -39,9 +39,7 @@

     isEditable: () ->
       if @model.asPage()?.get('loaded') and @model.isDraft()
-        console.log @model.asPage().get('canPublish')
         edit = @model.asPage()?.get('canPublish')
-        console.log edit
         if edit isnt undefined and edit.toString().indexOf(session.get('id')) >= 0 and not @model.asPage()?.isDraft()
           return true


Add another spec for matching inventory entries.

--- src/spec/updater/inventory-updater.spec.coffee
+++ src/spec/updater/inventory-updater.spec.coffee
@@ -167,6 +167,7 @@
     @updater.existingInventoryEntries = [
       { id: '1', sku: 'foo', quantityOnStock: 3 }
       { id: '2', sku: 'foo', quantityOnStock: 7, supplyChannel: { id: 'channel123' } }
+      { id: '3', sku: 'foo', quantityOnStock: 9, supplyChannel: { id: 'channelX' } }
     ]
     entry = @updater.match sku: 'foo'
     expect(entry).toEqual { id: '1', sku: 'foo', quantityOnStock: 3 }
@@ -184,6 +185,16 @@
         id: 'channel123'
     expect(entry).toEqual expected

+    stock.supplyChannel.id = 'channelX'
+    expected =
+      id: '3'
+      sku: 'foo'
+      quantityOnStock: 9
+      supplyChannel:
+        id: 'channelX'
+    entry = @updater.match stock
+    expect(entry).toEqual expected
+
 describe '#update', ->
   beforeEach ->
     @updater = new InventoryUpdater config: Config


Add spec for handling multiple packages in the trace

--- spec/notifications-spec.coffee
+++ spec/notifications-spec.coffee
@@ -107,6 +107,33 @@
           fatalError = notificationContainer.querySelector('atom-notification.fatal')
           expect(notificationContainer.childNodes.length).toBe 0
           expect(fatalError).toBe null
+
+      describe "when there are multiple packages in the stack trace", ->
+        fatalError = null
+        beforeEach ->
+          stack = """
+            TypeError: undefined is not a function
+              at Object.module.exports.Pane.promptToSaveItem [as defaultSavePrompt] (/Applications/Atom.app/Contents/Resources/app/src/pane.js:490:23)
+              at Pane.promptToSaveItem (/Users/someguy/.atom/packages/save-session/lib/save-prompt.coffee:21:15)
+              at Pane.module.exports.Pane.destroyItem (/Applications/Atom.app/Contents/Resources/app/src/pane.js:442:18)
+              at HTMLDivElement.<anonymous> (/Applications/Atom.app/Contents/Resources/app/node_modules/tabs/lib/tab-bar-view.js:174:22)
+              at space-pen-ul.jQuery.event.dispatch (/Applications/Atom.app/Contents/Resources/app/node_modules/archive-view/node_modules/atom-space-pen-views/node_modules/space-pen/vendor/jquery.js:4676:9)
+              at space-pen-ul.elemData.handle (/Applications/Atom.app/Contents/Resources/app/node_modules/archive-view/node_modules/atom-space-pen-views/node_modules/space-pen/vendor/jquery.js:4360:46)
+          """
+          detail = 'ok'
+
+          atom.notifications.addFatalError('TypeError: undefined', {detail, stack})
+          notificationContainer = workspaceElement.querySelector('atom-notifications')
+          fatalError = notificationContainer.querySelector('atom-notification.fatal')
+
+          fs = require 'fs'
+          spyOn(fs, 'realpathSync').andCallFake (p) -> p
+          spyOn(fatalError, 'getPackagePathsByPackageName').andCallFake ->
+            'save-session': '/Users/someguy/.atom/packages/save-session'
+            'tabs': '/Applications/Atom.app/Contents/Resources/app/node_modules/tabs'
+
+        it "chooses the first package in the trace", ->
+          expect(fatalError.getPackageName()).toBe 'save-session'

       describe "when an exception is thrown from a package", ->
         beforeEach ->


Abort pagination load on navigation

Abort pagination load on navigation

--- resources/assets/coffee/react/beatmaps/main.coffee
+++ resources/assets/coffee/react/beatmaps/main.coffee
@@ -130,7 +130,7 @@

     @setState paging: pagingState

-    $.ajax @state.paging.url,
+    @xhr.pagination = $.ajax @state.paging.url,
       method: 'get'
       dataType: 'json'
       data: _.extend @buildSearchQuery(), page: @state.paging.page + 1


sql function

sql function

--- shared/code/scraperwiki.coffee
+++ shared/code/scraperwiki.coffee
@@ -33,6 +33,21 @@
     options.error = error
   $.ajax options

+scraperwiki.sql = (sql, success, error) ->
+  settings = scraperwiki.readSettings()
+  token = settings.token
+  options =
+    url: "#{window.location.protocol}//#{window.location.host}/#{scraperwiki.boxName}/#{token}/sqlite"
+    type: "GET"
+    dataType: "json"
+    data:
+      q: sql
+  if success?
+    options.success = success
+  if error?
+    options.error = error
+  $.ajax options
+
 scraperwiki.readSettings = ->
   return null if window.location.hash is ''
   hash = window.location.hash.substr(1)


Beef up Native.addMenuItem spec

Beef up Native.addMenuItem spec

--- spec/stdlib/native-spec.coffee
+++ spec/stdlib/native-spec.coffee
@@ -17,8 +17,25 @@
     it "adds the item at the path terminus to the main menu, adding submenus as needed", ->
       initialMenuCount = mainMenu.itemArray.length

-      nativeModule.addMenuItem('Submenu > Item')
+      nativeModule.addMenuItem('Submenu 1 > Item 1')

       expect(mainMenu.itemArray.length).toBe initialMenuCount + 1
+      submenu1 = mainMenu.itemWithTitle('Submenu 1').submenu
+      item1 = submenu1.itemWithTitle('Item 1')
+      expect(item1).toBeDefined()

+      nativeModule.addMenuItem('Submenu 1 > Item 2')

+      expect(mainMenu.itemArray.length).toBe initialMenuCount + 1
+      expect(submenu1.itemArray.length).toBe 2
+      item1 = submenu1.itemWithTitle('Item 2')
+      expect(item1).toBeDefined()
+
+      nativeModule.addMenuItem('Submenu 2 > Item 1')
+
+      expect(mainMenu.itemArray.length).toBe initialMenuCount + 2
+      expect(submenu1.itemArray.length).toBe 2
+      submenu1 = mainMenu.itemWithTitle('Submenu 2').submenu
+      item1 = submenu1.itemWithTitle('Item 1')
+      expect(item1).toBeDefined()
+


:construction: Add disabled test for horizontal scroll on move-right

--- spec/table-element-spec.coffee
+++ spec/table-element-spec.coffee
@@ -1080,6 +1080,19 @@
       tableElement.moveRight()
       expect(tableElement.getActiveCell().getValue()).toEqual('row0')

+    xdescribe 'when the absoluteColumnsWidths setting is enabled', ->
+      describe 'and the last column is partially hidden', ->
+        beforeEach ->
+          tableElement.setAbsoluteColumnsWidths(true)
+          tableElement.setColumnsWidths([100, 200, 300])
+
+          tableElement.moveRight()
+          tableElement.moveRight()
+
+        it 'scrolls the view to the right', ->
+          rows = tableShadowRoot.querySelector('.table-edit-rows')
+          expect(rows.scrollLeft).not.toEqual(0)
+
   describe '::moveLeft', ->
     it 'requests an update', ->
       spyOn(tableElement, 'requestUpdate')


:lipstick: Use single quote for test description

--- spec/table-element-spec.coffee
+++ spec/table-element-spec.coffee
@@ -136,7 +136,7 @@
         expect(rows.length).toEqual(1)
         expect(rows[0].querySelectorAll('.table-edit-cell').length).toEqual(1)

-    it "honors the absolute-columns-widths attribute", ->
+    it 'honors the absolute-columns-widths attribute', ->
       container.innerHTML = "<atom-table-editor absolute-columns-widths>"
       element = container.firstChild
       expect(element.absoluteColumnsWidths).toBeTruthy()


:shirt: Unnecessary fat arrows

:shirt: Unnecessary fat arrows

--- spec/atom-environment-spec.coffee
+++ spec/atom-environment-spec.coffee
@@ -325,7 +325,7 @@
     describe "deserialization failures", ->

       it "propagates project state restoration failures", ->
-        spyOn(atom.project, 'deserialize').andCallFake =>
+        spyOn(atom.project, 'deserialize').andCallFake ->
           err = new Error('deserialization failure')
           err.missingProjectPaths = ['/foo']
           Promise.reject(err)
@@ -337,7 +337,7 @@
             {description: 'Project directory `/foo` is no longer on disk.'}

       it "accumulates and reports two errors with one notification", ->
-        spyOn(atom.project, 'deserialize').andCallFake =>
+        spyOn(atom.project, 'deserialize').andCallFake ->
           err = new Error('deserialization failure')
           err.missingProjectPaths = ['/foo', '/wat']
           Promise.reject(err)
@@ -349,7 +349,7 @@
             {description: 'Project directories `/foo` and `/wat` are no longer on disk.'}

       it "accumulates and reports three+ errors with one notification", ->
-        spyOn(atom.project, 'deserialize').andCallFake =>
+        spyOn(atom.project, 'deserialize').andCallFake ->
           err = new Error('deserialization failure')
           err.missingProjectPaths = ['/foo', '/wat', '/stuff', '/things']
           Promise.reject(err)


remove unneeded parenthesis

remove unneeded parenthesis

--- spec/common/basetypes.spec.coffee
+++ spec/common/basetypes.spec.coffee
@@ -11,22 +11,22 @@
     v = new bt.Value

   it 'throws when using a plain Effect', ->
-    expect(-> v.addEffect(new bt.Effect)).toThrow()
+    expect(-> v.addEffect new bt.Effect).toThrow()

   it 'sets the value when using InitialValue', ->
-    v.addEffect(new bt.InitialValue(1))
+    v.addEffect new bt.InitialValue 1
     expect(v.value).toBe(1)

   it 'throws when setting two initial values', ->
-    v.addEffect(new bt.InitialValue(1))
-    expect(-> v.addEffect(new bt.InitialValue(2))).toThrow()
+    v.addEffect new bt.InitialValue 1
+    expect(-> v.addEffect(new bt.InitialValue 2)).toThrow()

   it 'throws if trying to use a mod effect without an initial', ->
-    expect(-> v.addEffect(new bt.ModValue(1))).toThrow()
+    expect(-> v.addEffect(new bt.ModValue 1)).toThrow()

   it 'applies the mod effect after an initial', ->
-    v.addEffect(new bt.InitialValue(1))
-    v.addEffect(new bt.ModValue(+1))
+    v.addEffect new bt.InitialValue 1
+    v.addEffect new bt.ModValue +1
     expect(v.value).toBe(2)

   it 'applies mod effects cummulatively', ->


When adding students to course by email address, save the address in the course file.

--- salvus/page/editor_course.cjsx
+++ salvus/page/editor_course.cjsx
@@ -245,15 +245,11 @@
             # students = array of account_id or email_address
             # New student_id's will be constructed randomly for each student
             student_ids = []
-            for id in students
+            for x in students
                 student_id = misc.uuid()
                 student_ids.push(student_id)
                 obj = {table:'students', student_id:student_id}
-                if '@' in id
-                    obj.email_address = id
-                else
-                    obj.account_id = id
-                syncdb.update(set:{}, where:obj)
+                syncdb.update(set:x, where:obj)
             syncdb.save()
             f = (student_id, cb) =>
                 async.series([
@@ -1445,7 +1441,19 @@
         </Button>

     add_selected_students : ->
-        @props.flux.getActions(@props.name).add_students(@refs.add_select.getSelectedOptions())
+        emails = {}
+        for x in @state.add_select
+            if x.account_id?
+                emails[x.account_id] = x.email_address
+        students = []
+        for y in @refs.add_select.getSelectedOptions()
+            if misc.is_valid_uuid_string(y)
+                students.push
+                    account_id    : y
+                    email_address : emails[y]
+            else
+                students.push({email_address:y})
+        @props.flux.getActions(@props.name).add_students(students)
         @setState(err:undefined, add_select:undefined, add_search:'')

     render_add_selector_options : ->


course managment: first version done, modulo bugs/or major ui issues found in testing

--- salvus/page/editor_course.cjsx
+++ salvus/page/editor_course.cjsx
@@ -24,13 +24,9 @@
 ###
 TODO:

-- [ ] (0:45?) (0:55) while doing any of three steps of workflow, set something in database and store, which locks things (with a minute limit and spinner) to prevent double click.
-- [ ] (0:45?) re-assign/re-copy/re-collect need to have ... and confirm, since they are dangerous.
-- [ ] (0:30?) optimization -- get rid of the open/close/stop closures.
-

 NEXT VERSION (after a release):
-- [ ] (1:00?) ui -- maybe do a max-height on listing of student assignments or somewhere and overfloat auto
+- [ ] (1:00?) ui -- maybe do a max-height on listing of student assignments or somewhere and overflow auto
 - [ ] (1:00?) provide a way to enable/disable tooltips on a per-application basis
 - [ ] (1:30?) #speed cache stuff/optimize for speed
 - [ ] (0:30?) #unclear rename "Settings" to something else, maybe "Control".
@@ -41,6 +37,8 @@
 - [ ] (8:00?) #unclear way to show other viewers that a field is being actively edited by a user (no idea how to do this in react)

 DONE:
+- [x] (0:45?) (0:20) re-assign/re-copy/re-collect need to have ... and confirm, since they are dangerous.
+- [x] (0:45?) (0:55) while doing any of three steps of workflow, set something in database and store, which locks things (with a minute limit and spinner) to prevent double click.
 - [x] (0:45?) (2:30) make confirm copies have nice text and look nice
 - [x] (0:45?) (0:37) xs mobile assignment looks bad -- need a fullscreen toggle thing.
 - [x] (1:00?) (1:17) make it so creating a project does not open it until user explicitly opens it.
@@ -664,18 +662,15 @@
                 @_update(set:{"#{type}":x}, where:where)

         _start_copy: (assignment, student, type) =>
-            console.log("_start_copy")
             if student? and assignment?
                 store = get_store(); student = store.get_student(student); assignment = store.get_assignment(assignment)
                 where = {table:'assignments', assignment_id:assignment.get('assignment_id')}
                 x = syncdb.select_one(where:where)?[type] ? {}
                 y = (x[student.get('student_id')]) ? {}
                 if y.start? and new Date() - y.start <= 15000
-                    console.log("_start_copy: bail")
                     return true  # never retry a copy until at least 15 seconds later.
                 y.start = misc.mswalltime()
                 x[student.get('student_id')] = y
-                console.log("_start_copy: do it")
                 @_update(set:{"#{type}":x}, where:where)
             return false

@@ -1587,16 +1582,29 @@
             {name}ed <BigTime date={time} />

-    render_open_recopy: (name, open, copy, copy_tip, open_tip) ->
-        if name == "Return"
-            placement = 'left'
-        <ButtonGroup key='open_recopy'>
-            <Button key="copy" bsStyle='warning' onClick={copy}>
+    render_open_recopy_confirm: (name, open, copy, copy_tip, open_tip, placement) ->
+        key = "recopy_#{name}"
+        if @state[key]
+            v = []
+            v.push <Button key="copy_confirm" bsStyle="danger" onClick={=>@setState("#{key}":false);copy()}>
+                <Icon name="share-square-o" rotate={"180" if name=='Collect'}/> Yes, re{name.toLowerCase()}
+            </Button>
+            v.push <Button key="copy_cancel" onClick={=>@setState("#{key}":false);}>
+                <Icon name="share-square-o" rotate={"180" if name=='Collect'}/> Cancel
+            </Button>
+            return v
+        else
+            <Button key="copy" bsStyle='warning' onClick={=>@setState("#{key}":true)}>
                 <Tip title={name} placement={placement}
                     tip={{copy_tip}}>
-                    <Icon name='share-square-o' rotate={"180" if name=='Collect'}/> Re-{name.toLowerCase()}
+                    <Icon name='share-square-o' rotate={"180" if name=='Collect'}/> Re{name.toLowerCase()}...
                 </Tip>
             </Button>
+
+    render_open_recopy: (name, open, copy, copy_tip, open_tip) ->
+        placement = if name == 'Return' then 'left' else 'right'
+        <ButtonGroup key='open_recopy'>
+            {@render_open_recopy_confirm(name, open, copy, copy_tip, open_tip, placement)}
             <Button key='open'  onClick={open}>
                 <Tip title="Open assignment" placement={placement} tip={open_tip}>
                     <Icon name="folder-open-o" /> Open
@@ -1906,11 +1914,11 @@
     copy_confirm_all_caution: (step) ->
         switch step
             when 'assignment'
-                return "This will re-copy all of the files to them.  CAUTION: if you update a file that a student has also worked on, their work will get copied to a backup file ending in a tilde, or possibly only be available in snapshots."
+                return "This will recopy all of the files to them.  CAUTION: if you update a file that a student has also worked on, their work will get copied to a backup file ending in a tilde, or possibly only be available in snapshots."
             when 'collect'
-                return "This will re-collect all of the homework from them.  CAUTION: if you have graded/edited a file that a student has updated, you work will get copied to a backup file ending in a tilde, or possibly only be available in snapshots."
+                return "This will recollect all of the homework from them.  CAUTION: if you have graded/edited a file that a student has updated, you work will get copied to a backup file ending in a tilde, or possibly only be available in snapshots."
             when 'return_graded'
-                return "This will re-return all of the graded files to them."
+                return "This will rereturn all of the graded files to them."

     render_copy_confirm_overwrite_all: (step, status) ->


fixed priority sorting

fixed priority sorting

--- src/art/engine/events/pointer_event_manager.coffee
+++ src/art/engine/events/pointer_event_manager.coffee
@@ -179,7 +179,7 @@
       if e = @capturingElement
         f e
       else
-        f e for e in @currentFocusedPath
+        f e for e in prioritySortElements @currentFocusedPath

     queueMouseEvents: (type, pointer, timeStampInPerformanceSeconds) ->
       @queuePointerEventForElements @currentMousePath, type, pointer, timeStampInPerformanceSeconds


No error on SIGHUP failure and renamed "nuke" to "reinitialize".

--- client/landing/app/MainApp/sidebar/virtualizationcontrols.coffee
+++ client/landing/app/MainApp/sidebar/virtualizationcontrols.coffee
@@ -2,22 +2,22 @@

   constructor:->
     options =
-      cssClass      : "virt-controls"
-      buttons       :
-        "Start"     :
-          callback  : ->
+      cssClass         : "virt-controls"
+      buttons          :
+        "Start"        :
+          callback     : ->
             KD.singletons.kiteController.run
               kiteName: 'os',
               method: 'vm.start'
-        "Stop"      :
-          callback  : ->
+        "Stop"         :
+          callback     : ->
             KD.singletons.kiteController.run
               kiteName: 'os',
               method: 'vm.stop'
-        "Nuke"      :
-          callback  : ->
+        "Reinitialize" :
+          callback     : ->
             KD.singletons.kiteController.run
               kiteName: 'os',
-              method: 'vm.nuke'
+              method: 'vm.reinitialize'

     super options


My typing skillz, fixed

My typing skillz, fixed

--- scripts/favorite-images.coffee
+++ scripts/favorite-images.coffee
@@ -2,6 +2,7 @@
 #
 # dancing cat
 #
-module.export = (robot) ->
+
+module.exports = (robot) ->
   robot.respond /show me dancing cat/i, (msg) ->
     msg.send "https://d1ij7zv8zivhs3.cloudfront.net/assets/2719117/original/animated-dancing%20cat.gif"


Fix spacing

Fix spacing

--- core/app/backbone/views/fact_relations/fact_relation_view.coffee
+++ core/app/backbone/views/fact_relations/fact_relation_view.coffee
@@ -32,7 +32,7 @@
   current_opinion: -> @model.get('current_user_opinion')

   on_up_vote: ->   @open_vote_popup '.supporting', FactRelationVoteUpView
-  on_down_vote: -> @open_vote_popup '.weakening', FactRelationVoteDownView
+  on_down_vote: -> @open_vote_popup '.weakening',  FactRelationVoteDownView

   open_vote_popup: (selector, view_klass) ->
     return if @popoverOpened selector


tweak transloadit size

tweak transloadit size

--- public/javascripts/people.coffee
+++ public/javascripts/people.coffee
@@ -83,7 +83,7 @@
       onStart: -> $i.find('.spinner').show()
       onSuccess: (data) ->
         $i.find('.spinner').hide()
-        image_url = data.results.w80[0].url
+        image_url = data.results.w160[0].url
         $i.find('img.avatar').attr('src', image_url).end()
           .find('input').val(image_url).end()


detect collision with walls

detect collision with walls

--- public/javascripts/tetris.coffee
+++ public/javascripts/tetris.coffee
@@ -17,7 +17,7 @@
       @viewportOffset.x + (@viewport.x/2) - 2
     levelSize: 16 # blocks
     viewportOffset: # blocks
-      x: 13
+      x: 0
       y: 0
     shapes: {}
     playerID: makeGuid()
@@ -116,9 +116,6 @@
        {x: x, y: y}

     canMoveDown: ->
-      floor = Tetris.height - 1
-      for i in [0,1,2,3]
-        return false if @blockPosition(i).y == floor
       return @canMoveTo(0, +1)

     canMoveTo: (xOffset, yOffset, rotation=null) ->
@@ -126,6 +123,9 @@
         x = @blockPosition(i, rotation).x + xOffset
         y = @blockPosition(i, rotation).y + yOffset
         return false if Tetris.blocks.contains(x, y)
+        return false if x < 0
+        return false if x >= Tetris.levelSize
+        return false if y >= Tetris.viewport.y
       return true

     draw: (c) ->


Moved hide to CreateButton

Moved hide to CreateButton

--- js-library/app/js/jail_iframe/classes/buttons.coffee
+++ js-library/app/js/jail_iframe/classes/buttons.coffee
@@ -24,10 +24,6 @@
     @_updatePosition()

   _updatePosition: =>
-
-  hide: =>
-    @frame.fadeOut()
-    @_visible = false

 class FactlinkJailRoot.CreateButton extends Button
@@ -84,6 +80,10 @@
       top: top
       left: left

+  hide: =>
+    @frame.fadeOut()
+    @_visible = false
+

 class IconButton extends Button


Update loading indicator message.

Update loading indicator message.

--- Framework/Views/Controls/LoadingIndicatorView_Framework.coffee
+++ Framework/Views/Controls/LoadingIndicatorView_Framework.coffee
@@ -24,7 +24,7 @@
       @add(@loadingView)
     else if Ti.Platform.osname == "android"
       @activityIndicator = Ti.UI.createActivityIndicator({
-        message: "Loading Properties..."
+        message: "Loading..."
         width: Ti.UI.SIZE, height: Ti.UI.SIZE
         color: "#333"
         cancelable: true


"Oxfordization"

"Oxfordization"

--- src/components/course-settings/archive-period.cjsx
+++ src/components/course-settings/archive-period.cjsx
@@ -36,7 +36,7 @@

         Archiving means
         this <CourseGroupingLabel lowercase courseId={@props.courseId} /> will
-        not be visible on your dashboard, student scores or export.
+        not be visible on your dashboard, student scores, or export.

         <AsyncButton className='archive-section' onClick={@performArchive}


display 60 photos by page

display 60 photos by page

--- server/controllers/file.coffee
+++ server/controllers/file.coffee
@@ -4,7 +4,7 @@
 fs = require 'fs'
 im = require 'imagemagick'
 onThumbCreation = require('../../init').onThumbCreation
-fileByPage = 4 * 10
+fileByPage = 5 * 12

 # Get given file, returns 404 if photo is not found.
 module.exports.fetch = (req, res, next, id) ->


Update Ameli's konnector to match the last website version (#470)

--- server/konnectors/ameli.coffee
+++ server/konnectors/ameli.coffee
@@ -28,7 +28,7 @@
 logIn = (requiredFields, billInfos, data, next) ->

     loginUrl = "https://assure.ameli.fr/PortailAS/appmanager/PortailAS/" + \
-    "assure?_nfpb=true&_pageLabel=as_login_page"
+    "assure?_somtc=true"

     submitUrl = "https://assure.ameli.fr/PortailAS/appmanager/PortailAS/" + \
     "assure?_nfpb=true&_windowLabel=connexioncompte_2&connexioncompte_2_" + \
@@ -36,7 +36,10 @@
     "_pageLabel=as_login_page"

     reimbursementUrl = "https://assure.ameli.fr/PortailAS/appmanager/" + \
-    "PortailAS/assure?_nfpb=true&_pageLabel=as_dernier_paiement_page"
+    "PortailAS/assure?_nfpb=true&_pageLabel=as_paiements_page"
+
+    refererUrl = "https://assure.ameli.fr/PortailAS/appmanager/" + \
+    "PortailAS/assure?_nfpb=true&_pageLabel=as_login_page"

     form =
         "connexioncompte_2numSecuriteSociale": requiredFields.login
@@ -53,41 +56,46 @@

     # First request to get the cookie
     request options, (err, res, body) ->
-        return next err if err?
-
-        loginOptions =
-            method: 'POST'
-            form: form
-            jar: true
-            strictSSL: false
-            url: submitUrl
-            headers:
-                'Cookie': res.headers['set-cookie']
-                'Referer': 'https://assure.ameli.fr/PortailAS/appmanager/' + \
-                           'PortailAS/assure?_nfpb=true&_pageLabel=' + \
-                           'as_login_page'
-
-        # Second request to authenticate
-        request loginOptions, (err, res, body) ->
-
-            isNotLogedIn = body.indexOf('Connexion à mon compte') > -1
-
-            if err or isNotLogedIn
-                log.error "Authentification error"
-                next 'bad credentials'
-            else
-                reimbursementOptions =
-                    method: 'GET'
-                    jar: true
-                    strictSSL: false
-                    url: reimbursementUrl
-
-                # Last request to get the reimbursements
-                request reimbursementOptions, (err, res, body) ->
-                    if err then next err
-                    else
-                        data.html = body
-                        next()
+        if err
+            log.error err
+            return next 'request error'
+        else
+            loginOptions =
+                method: 'POST'
+                form: form
+                jar: true
+                strictSSL: false
+                url: submitUrl
+                headers:
+                    'Cookie': res.headers['set-cookie']
+                    'Referer': refererUrl
+
+            # Second request to authenticate
+            request loginOptions, (err, res, body) ->
+                if err
+                    log.error err
+                    next 'bad credentials'
+                else if body.indexOf('Connexion à mon compte') > -1
+                    log.error 'Authentication error'
+                    next 'bad credentials'
+                else
+                    reimbursementOptions =
+                        method: 'GET'
+                        jar: true
+                        strictSSL: false
+                        headers:
+                            'Cookie': res.headers['set-cookie']
+                            'Referer': refererUrl
+                        url: reimbursementUrl
+
+                    # Last request to get the reimbursements page
+                    request reimbursementOptions, (err, res, body) ->
+                        if err
+                            log.error err
+                            return next 'request error'
+                        else
+                            data.html = body
+                            next()

 # Parse the fetched page to extract bill data.
@@ -98,56 +106,100 @@

     $ = cheerio.load data.html

-    $('#tabDerniersPaiements tbody tr').each ->
-        date = $($(this).find('td').get(0)).text()
-        subtype = $($(this).find('td').get(1)).text()
-
-        amount = $($(this).find('td').get(2)).text()
-        amount = amount.replace(' euros', '').replace(',','.')
-        amount = parseFloat amount
-
-        # Get the details url
-        detailsUrl = $($(this).find('td a').get(1)).attr('href')
-        # Remove the unecessary port to avoid buggy request
-        detailsUrl = detailsUrl.replace(':443', '')
-
-        bill =
-            amount: amount
-            type: 'health'
-            subtype: subtype
-            date: moment date, 'DD/MM/YYYY'
-            vendor: 'Ameli'
-            detailsUrl: detailsUrl
-
-        healthBills.fetched.push bill if bill.amount?
-
-    # For each bill, get the pdf
-    async.each healthBills.fetched, getPdf, (err) ->
-        next err
-
-
-# Retrieve pdf url
-getPdf = (bill, callback) ->
-    detailsUrl = bill.detailsUrl
-
-    options =
-        method: 'GET'
+    # Get the start and end date to generate the bill's url
+    startDate = $('#paiements_1dateDebut').attr('value')
+    endDate = $('#paiements_1dateFin').attr('value')
+
+    baseUrl = "https://assure.ameli.fr/PortailAS/paiements.do?actionEvt="
+
+    billUrl = baseUrl + "afficherPaiementsComplementaires&DateDebut="
+    billUrl += (startDate + "&DateFin=" + endDate)
+    billUrl += "&Beneficiaire=tout_selectionner&afficherReleves=false&" + \
+    "afficherIJ=false&afficherInva=false&afficherRentes=false&afficherRS=" + \
+    "false&indexPaiement=&idNotif="
+
+    billOptions =
         jar: true
         strictSSL: false
-        url: detailsUrl
-
-    # Request to get the pdf url
-    request options, (err, res, body) ->
-        if err? or res.statusCode isnt 200
-            callback new Error 'Pdf not found'
+        url: billUrl
+
+    request billOptions, (err, res, body) ->
+        if err
+            log.error err
+            return next 'request error'
         else
-            html = cheerio.load body
-            pdfUrl = "https://assure.ameli.fr"
-            pdfUrl += html('.r_lien_pdf').attr('href')
-            # Remove all the dirty escape characters...
-            pdfUrl = pdfUrl.replace(/(?:\r\n|\r|\n|\t)/g, '')
-            bill.pdfurl = pdfUrl
-            callback null
+            $ = cheerio.load body
+            i = 0
+
+            # Each bloc represents a month that includes 0 to n reimbursement
+            $('.blocParMois').each ->
+
+                $('[id^=lignePaiement' + i++ + ']').each ->
+
+                    amount = $($(this).find('.col-montant').get(0)).text()
+                    amount = amount.replace(' €', '').replace(',','.')
+                    amount = parseFloat amount
+
+                    month = $($(this).find('.col-date .mois').get(0)).text()
+                    day = $($(this).find('.col-date .jour').get(0)).text()
+                    date = day + ' ' + month
+                    moment.locale 'fr'
+                    date = moment(date, 'Do MMMM YYYY')
+
+                    label = $($(this).find('.col-label').get(0)).text()
+
+                    # Retrieve and extract the infos needed to generate the pdf
+                    attrInfos = $(this).attr('onclick')
+                    tokens = attrInfos.split("'")
+
+                    idPaiement = tokens[1]
+                    naturePaiement = tokens[3]
+                    indexGroupe = tokens[5]
+                    indexPaiement = tokens[7]
+
+                    detailsUrl =  baseUrl + "chargerDetailPaiements&"
+                    detailsUrl += "idPaiement=" + idPaiement  + "&"
+                    detailsUrl += "naturePaiement=" + naturePaiement + "&"
+                    detailsUrl += "indexGroupe=" + indexGroupe + "&"
+                    detailsUrl += "indexPaiement=" + indexPaiement
+
+                    lineId = indexGroupe + indexPaiement
+
+                    bill =
+                        amount: amount
+                        type: 'health'
+                        subtype: label
+                        date: date
+                        vendor: 'Ameli'
+                        lineId: lineId
+                        detailsUrl: detailsUrl
+
+                    healthBills.fetched.push bill if bill.amount?
+
+            async.each healthBills.fetched, getPdf, (err) ->
+                next err
+
+
+getPdf = (bill, callback) ->
+
+    # Request the generated url to get the detailed pdf
+    detailsOptions =
+        jar: true
+        strictSSL: false
+        url: bill.detailsUrl
+
+    request detailsOptions, (err, res, body) ->
+        if err
+            log.error err
+            return callback 'request error'
+        else
+            $ = cheerio.load body
+
+            pdfUrl = $('[id=liendowndecompte' + bill.lineId + ']').attr('href')
+            if pdfUrl
+                pdfUrl = "https://assure.ameli.fr" + pdfUrl
+                bill.pdfurl = pdfUrl
+                callback null

 buildNotification = (requiredFields, healthBills, data, next) ->
@@ -160,10 +212,11 @@

     next()

+
 customLinkBankOperation = (requiredFields, healthBills, data, next) ->
     identifier = 'C.P.A.M.'
-    if requiredFields.bank_identifier isnt ""
-        identifier = requiredFields.bank_identifier
+    bankIdentifier = requiredFields.bank_identifier
+    identifier = bankIdentifier if bankIdentifier? and bankIdentifier isnt ""

     linkBankOperation(
         log: log


use polyfill for closest on ie11 (#4463)

--- angular/core/config/provide.coffee
+++ angular/core/config/provide.coffee
@@ -1,4 +1,21 @@
 angular.module('loomioApp').config ($provide) ->
+  # polyfill for closest for ie11
+  # from https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
+  if !Element::matches
+    Element::matches = Element::msMatchesSelector or Element::webkitMatchesSelector
+
+  if !Element::closest
+    Element::closest = (s) ->
+      el = this
+      if !document.documentElement.contains(el)
+        return null
+      loop
+        if el.matches(s)
+          return el
+        el = el.parentElement or el.parentNode
+        unless el != null
+          break
+      null
   # a decorator to allow mentio to work within modals
   # https://github.com/jeff-collins/ment.io/issues/68#issuecomment-200746901
   $provide.decorator 'mentioMenuDirective', ($delegate) ->


block active_admin_size

block active_admin_size

--- app/assets/javascripts/active_admin/lib/modal_dialog.js.coffee
+++ app/assets/javascripts/active_admin/lib/modal_dialog.js.coffee
@@ -56,8 +56,7 @@
     open: (event, ui) ->
       $('body').trigger 'modal_dialog:after_open', [form]
     dialogClass: 'active_admin_dialog'
-    resizable: true
-    width:'auto'
+    width:'700px'
     buttons:
       OK: ->
         callback $(@).serializeObject()


Added debug log to get more infos about not reproducible issue.

--- app/assets/javascripts/app/lib/app_post/task_manager.js.coffee
+++ app/assets/javascripts/app/lib/app_post/task_manager.js.coffee
@@ -130,8 +130,9 @@

     # create new task if not exists
     task = @get( key )
-    #console.log('add', key, callback, params, to_not_show, task)
+    console.log('add', key, callback, params, to_not_show, task)
     if !task
+      console.log('add, create new taskbar in backend')
       task = new App.Taskbar
       task.load(
         key:      key
@@ -211,7 +212,7 @@

   startController: (key, callback, params, to_not_show) =>

-#    console.log('controller started...', callback, key, params)
+    console.log('controller start try...', callback, key)

     # activate controller
     worker = @worker( key )
@@ -221,6 +222,7 @@
     # return if controller is already started
     return if @workersStarted[key]
     @workersStarted[key] = true
+    console.log('controller start now...', callback, key)

     # create new controller instanz
     params_app = _.clone(params)
@@ -231,11 +233,14 @@
       params_app['doNotLog'] = 1
     a = new App[callback]( params_app )
     @workers[ key ] = a
+    console.log('controller start now 2...', callback, key)

     # activate controller
     if !to_not_show
+      console.log('controller start now 2 activate...', callback, key)
       a.activate()

+    console.log('controller start now 2 return...', callback, key)
     return a

   get: ( key ) =>


Prevent UI from suggesting you can message yourself

--- app/assets/javascripts/discourse/views/composer_view.js.coffee
+++ app/assets/javascripts/discourse/views/composer_view.js.coffee
@@ -129,7 +129,7 @@
         Discourse.UserSearch.search
           term: term,
           callback: callback,
-          exclude: selected
+          exclude: selected.concat [Discourse.get('currentUser.username')]
       onChangeItems: (items) =>
         items = $.map items, (i) -> if i.username then i.username else i
         @set('content.targetUsernames', items.join(","))


Use `op` instead `this` in deferred obj

Use `op` instead `this` in deferred obj

Since jQuery 3 `.resolve`, `.reject`, and `.notify`
now set undefined context instead of using the promise
of the Deferred object with which they are associated

Fixed #387

--- app/assets/javascripts/uploadcare/utils/image-processor.coffee
+++ app/assets/javascripts/uploadcare/utils/image-processor.coffee
@@ -35,7 +35,7 @@

         ns.getExif(file).always (exif) ->
           df.notify(.2)
-          isJPEG = @state() is 'resolved'
+          isJPEG = op.state() is 'resolved'

           # start = new Date()
           op = ns.shrinkImage(img, settings)


Add Caption/Alt text and some fallback for cases where it didn't actually insert the image

--- plugins/image.coffee
+++ plugins/image.coffee
@@ -46,7 +46,7 @@

                         Caption<input type=\"text\" id=\"caption\" />
-                        <input type=\"submit\" />
+                        <button>Add Image</button>

SEARCH

@@ -55,7 +55,17 @@

             insertImage = () ->
                 #This may need to insert an image that does not have the same URL as the preview image, since it may be a different size
+
+                # Check if we have a selection and fall back to @lastSelection otherwise
+                try
+                    if not widget.options.editable.getSelection()
+                        throw new Error "SelectionNotSet"
+                catch error
+                    widget.options.editable.restoreSelection(widget.lastSelection)
+
                 document.execCommand "insertImage", null, $(this).attr('src')
+                img = document.getSelection().anchorNode.firstChild
+                jQuery(img).attr "alt", jQuery(".caption").value
                 widget._closeDialog()

             @options.dialog.find(".halloimage-activeImage").click insertImage
@@ -104,9 +114,12 @@
             # Update active Image
             jQuery("##{@options.uuid}-#{@widgetName}-activeImage").attr "src", jQuery(".#{@widgetName}-imageThumbnailActive").first().attr "src"

-            xposition = jQuery(@options.editable.element).offset().left + jQuery(@options.editable.element).outerWidth()
-            yposition = jQuery(@options.toolbar).offset().top - jQuery(document).scrollTop() - 30
+            # Save current caret point
+            @lastSelection = @options.editable.getSelection()

+            # Position correctly
+            xposition = jQuery(@options.editable.element).offset().left + jQuery(@options.editable.element).outerWidth() - 3 # 3 is the border width of the contenteditable border
+            yposition = jQuery(@options.toolbar).offset().top - jQuery(document).scrollTop() - 29
             @options.dialog.dialog("option", "position", [xposition, yposition])
             @options.dialog.dialog("open")


Inject skinLink hook implementations

Inject skinLink hook implementations

--- packages/shrub-skin/client/index.coffee
+++ packages/shrub-skin/client/index.coffee
@@ -11,8 +11,8 @@
 	# ## Implements hook `augmentDirective`
 	registrar.registerHook 'augmentDirective', (directive, path) -> [

-		'$cacheFactory', '$compile', '$http', '$interpolate', '$q', '$rootScope'
-		($cacheFactory, $compile, $http, $interpolate, $q, $rootScope) ->
+		'$cacheFactory', '$compile', '$http', '$injector', '$interpolate', '$q', '$rootScope'
+		($cacheFactory, $compile, $http, $injector, $interpolate, $q, $rootScope) ->

 			# Ensure ID is a candidate.
 			directive.candidateKeys ?= []
@@ -24,7 +24,7 @@
 			# Proxy link function to add our own directive retrieval and
 			# compilation step.
 			link = directive.link
-			directive.link = (scope, element, attr, controller) ->
+			directive.link = (scope, element, attr, controller, transclude) ->

 				candidateHooksInvoked = {}

@@ -113,7 +113,15 @@
 						continue if candidateHooksInvoked[hook]
 						candidateHooksInvoked[hook] = true
 						for f in pkgman.invokeFlat hook
-							f topLevelArgs...
+
+							$injector.invoke(
+								f, null
+								$scope: scope
+								$element: element
+								$attr: attr
+								$controller: controller
+								$transclude: transclude
+							)

 				applySkin = (skinKey) ->
 					currentSkinKey = skinKey


Use the new totoz server for default

Use the new totoz server for default

--- app/assets/javascripts/chat.coffee
+++ app/assets/javascripts/chat.coffee
@@ -10,7 +10,7 @@
     @board.find(".board-left .norloge").click @norloge
     @board.find("form").submit @postMessage
     @totoz_type = $.cookie("totoz-type")
-    @totoz_url  = $.cookie("totoz-url") or "https://totoz.moul.es/"
+    @totoz_url  = $.cookie("totoz-url") or "https://totoz.eu/img/"
     @norlogize      right for right in @board.find(".board-right")
     @norlogize_left left  for left  in @board.find(".board-left time").get().reverse()
     @board.on("mouseenter", ".board-left time", @left_highlitizer)


remove disconnection/logout detection until it is improved.

--- app/assets/javascripts/init.coffee
+++ app/assets/javascripts/init.coffee
@@ -103,39 +103,6 @@
     MCM.agentsToolbarRegion.show(agentsToolbarView)
     MCM.collectivesToolbarRegion.show(collectivesToolbarView)

-  # TODO: tidy this up, make a single view class of some sort
-  $(document).ajaxError (e, xhr, req) ->
-    if MCM.currentUser == undefined or req.url == "/users/sign_in.json" or xhr.statusText == "abort"
-      return
-
-    loginDialogs = $(".logout-notification").length
-    if xhr.status == 401 and loginDialogs < 1 and MCM.currentUser != undefined
-      if $(".disconnect-notification").length > 0
-        $(".disconnect-notification").modal('hide')
-
-      bootbox.dialog("You have been logged out.", [{
-        "label" : "Login",
-        "class" : "btn-primary"
-        "callback" : ->
-          window.location = "/"
-      }], { "classes" : "logout-notification" })
-    if xhr.status != 404 and $(".disconnect-notification").length < 1 and loginDialogs < 1
-      bootbox.dialog('

You seem to have been disconnected.', [{
-        "label" : "Reconnect",
-        "class" : "btn-primary"
-        "callback" : ->
-          $.ajax '/collectives', {
-            success: (rsp, check_status) ->
-              Backbone.history.loadUrl(Backbone.history.fragment)
-              $(".disconnect-notification").modal("hide")
-            complete: (rsp, check_status) ->
-              if check_status == "error"
-                disconnectNotification = HandlebarsTemplates['shared/notifications']( alertType : "error", message : "unable to reconnect", noDismiss : true )
-                $(".disconnect-notification .reconnect").html(disconnectNotification)
-          }
-          return false
-      }], { "classes" : "disconnect-notification" })
-
 $ ->
   MCM.start()
   Backbone.history.start()


File name change from coffee to js

File name change from coffee to js

--- app/assets/javascripts/oils.coffee
+++ app/assets/javascripts/oils.coffee
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://coffeescript.org/


BF-100 fixed y-conversion

BF-100 fixed y-conversion

--- app/assets/javascripts/view.coffee
+++ app/assets/javascripts/view.coffee
@@ -248,8 +248,8 @@
     cam2d.move [100*x, 0, 0]

   moveY : (y) ->
-    cam.move [0, -100*y, 0]
-    cam2d.move [0, -100*y, 0]
+    cam.move [0, 100*y, 0]
+    cam2d.move [0, 100*y, 0]

   moveZ : (z) ->
     cam.move [0, 0, 100*z]


The watcher is on slice, not volume.

The watcher is on slice, not volume.

--- app/javascripts/controllers.coffee
+++ app/javascripts/controllers.coffee
@@ -874,7 +874,7 @@

         # Redisplay the image when the slice changes.
         $scope.$watch 'slice', (slice, previous) ->
-          if volume != previous
+          if slice != previous
             display()
         #
         # TODO - implement the overlay watcher in conjunction with the


Fixed bundle loading.

Fixed bundle loading.

--- app/serializers/application.coffee
+++ app/serializers/application.coffee
@@ -11,10 +11,10 @@
     normalizedPayload = {}
   #   # Because the server returns NULL if no filters...
   #   # TODO Is this misbehavior on the server side?
-  #   debugger
+    # debugger
   #   return [] if payload == null
     # normalizedPayload[Ember.String.pluralize(type.typeKey)] = payload.Entries||payload
-    if payload.Type = "Bundle"
+    if payload.Type == "Bundle"
       console.log "Bundle came in"
       payload = payload.Entries
     @_super(store, type, payload, id, requestType)


Age in seconds

Age in seconds

--- test/cooldown.coffee
+++ test/cooldown.coffee
@@ -84,7 +84,7 @@
 test "should not blow up if cooldowns aren't specified", ->
   obj = GameObject()

-  obj.update()
+  obj.update(1)

   equals obj.I.age, 1, "should successfully update"

@@ -97,7 +97,7 @@
     target: 10

   5.times ->
-    obj.update()
+    obj.update(1)

   equals obj.I.health, 8

@@ -108,7 +108,7 @@
     target: 10

   5.times ->
-    obj.update()
+    obj.update(1)

   equals obj.I.health, 5


consistently use text variable for document tests

--- test/document.coffee
+++ test/document.coffee
@@ -11,33 +11,39 @@
 describe 'document', ->

     it 'parses a single element', ->
-        expect(mona.parse sut.document, "div:\n").to.eql [
+        text = "div:\n"
+        expect(mona.parse sut.document, text).to.eql [
             name: 'div', title: '', content: []
         ]

     it 'parses several elements', ->
-        expect(mona.parse sut.document, "div:\npre:\n").to.eql [
+        text = "div:\npre:\n"
+        expect(mona.parse sut.document, text).to.eql [
             {name: 'div', title: '', content: []}
             {name: 'pre', title: '', content: []}
         ]

     it 'parses a single paragraph', ->
-        expect(mona.parse sut.document, "Hello World\n").to.eql [
+        text = "Hello World\n"
+        expect(mona.parse sut.document, text).to.eql [
             [['Hello World']]
         ]

     it 'parses a line which is similar to an element', ->
-        expect(mona.parse sut.document, "Hello World:\n").to.eql [
+        text = "Hello World:\n"
+        expect(mona.parse sut.document, text).to.eql [
             [['Hello World:']]
         ]

     it 'ignores leading blank lines', ->
-        expect(mona.parse sut.document, "\n\nparagraph\n").to.eql [
+        text = "\n\nparagraph\n"
+        expect(mona.parse sut.document, text).to.eql [
             [['paragraph']]
         ]

     it 'ignores trailing blank lines', ->
-        expect(mona.parse sut.document, "\nparagraph\n\n").to.eql [
+        text = "\nparagraph\n\n"
+        expect(mona.parse sut.document, text).to.eql [
             [['paragraph']]]

     it 'parses multiple paragraphs', ->


Migrate .on, .once - ThangTypeEditView

Migrate .on, .once - ThangTypeEditView

--- app/views/editor/thang/edit.coffee
+++ app/views/editor/thang/edit.coffee
@@ -45,8 +45,8 @@
     @thangType.saveBackups = true
     @thangType.fetch()
     @thangType.loadSchema()
-    @thangType.schema().once 'sync', @onThangTypeSync, @
-    @thangType.once 'sync', @onThangTypeSync, @
+    @listenToOnce(@thangType.schema(), 'sync', @onThangTypeSync)
+    @listenToOnce(@thangType, 'sync', @onThangTypeSync)
     @refreshAnimation = _.debounce @refreshAnimation, 500

   onThangTypeSync: ->


Enabled some levels for testing.

Enabled some levels for testing.

--- app/views/play/WorldMapView.coffee
+++ app/views/play/WorldMapView.coffee
@@ -949,7 +949,6 @@
     id: 'a-fiery-trap'
     original: '5448330517d7283e051f9b9e'
     description: 'Patrol the village entrances, but stay defensive.'
-    disabled: not me.isAdmin()
     nextLevels:
       continue: 'ogre-encampment'
     x: 40.14
@@ -961,7 +960,6 @@
     difficulty: 1
     id: 'ogre-encampment'
     description: 'Recover stolen treasure from an ogre encampment.'
-    disabled: not me.isAdmin()
     nextLevels:
       continue: 'woodland-cleaver'
     x: 46.48
@@ -973,7 +971,6 @@
     difficulty: 1
     id: 'woodland-cleaver'
     description: 'Use your new cleave ability to fend off munchkins.'
-    disabled: not me.isAdmin()
     nextLevels:
       continue: 'shield-rush'
     x: 52.32
@@ -985,7 +982,6 @@
     difficulty: 1
     id: 'shield-rush'
     description: 'Combine cleave and shield to endure an ogre onslaught.'
-    disabled: not me.isAdmin()
     nextLevels:
       continue: 'peasant-protection'
     x: 58.54
@@ -997,7 +993,6 @@
     difficulty: 1
     id: 'peasant-protection'
     description: 'Stay close to Victor.'
-    disabled: not me.isAdmin()
     nextLevels:
       continue: 'munchkin-swarm'
     x: 64.37
@@ -1008,8 +1003,7 @@
     type: 'hero'
     difficulty: 1
     id: 'munchkin-swarm'
-    description: 'they eerywhere'
-    disabled: not me.isAdmin()
+    description: 'Loot a gigantic chest while surrounded by a swarm of ogre munchkins.'
     nextLevels:
       continue: 'coinucopia'
     x: 71.19


calback heaven

calback heaven

--- public/js/map.coffee
+++ public/js/map.coffee
@@ -77,10 +77,7 @@
 			fillColor: "light#{color}",
 			fillOpacity: 0.5

-		marker.on 'click', () ->
-			console.log 'FANTOMAS', ad.id
-			onMarkerClick ad.id
-
+		marker.on 'click', () -> onMarkerClick ad.id
 		marker.on 'mouseover', () ->
 			circle.addTo map
 		marker.on 'mouseout', () ->
@@ -119,11 +116,8 @@
 		map.setView coord, 14

-	onMapClick: (onMapClick) ->
-		onMapClick = onMapClick
-
-	onMarkerClick: (onMarkerClick) ->
-		onMarkerClick = onMarkerClick
+	onMapClick: (cb) -> onMapClick = cb
+	onMarkerClick: (cb) -> onMarkerClick = cb


Add helper function execGruntTask(task, callback)

--- test/node_gyp.coffee
+++ test/node_gyp.coffee
@@ -1,4 +1,12 @@
 should = require('chai').should()
+grunt = require 'grunt'
+
+gruntOptions =
+	gruntfile: __dirname + '/support/Gruntfile.coffee'
+
+execGruntTask = (task, callback) ->
+	grunt.tasks 'gyp:' + task, gruntOptions, ->
+		callback()

 describe 'grunt-node-gyp', ->
 	describe 'configure', ->


depdendencies :-/

depdendencies :-/

--- apps/artwork/test/templates.coffee
+++ apps/artwork/test/templates.coffee
@@ -32,6 +32,7 @@
       JS_EXT: '.js.gz'
       NODE_ENV: 'test'
       FACEBOOK_APP_NAMESPACE: 'artsyinc'
+      CURRENT_PATH: '/artwork/foo'
     @artwork = new Artwork fabricate 'artwork', sale_message: '$6,000'
     @artist = new Artist fabricate 'artist'
     @artists = new Artists [@artist]


Stop using mini editor for draft plans

Stop using mini editor for draft plans

--- tutor/src/components/course-calendar/plan.cjsx
+++ tutor/src/components/course-calendar/plan.cjsx
@@ -11,8 +11,7 @@
 CoursePlanLabel = require './plan-label'

 { CoursePlanDisplayEdit,
-  CoursePlanDisplayQuickLook,
-  CoursePlanDisplayMiniEditor} = require './plan-display'
+  CoursePlanDisplayQuickLook} = require './plan-display'

 {PlanPublishStore, PlanPublishActions} = require '../../flux/plan-publish'
 PlanHelper = require '../../helpers/plan'
@@ -199,9 +198,7 @@
     labelProps = {rangeDuration, plan, index, offset, offsetFromPlanStart}
     label = <CoursePlanLabel {...labelProps} ref="label#{index}"/>

-    DisplayComponent = if plan.is_draft
-      CoursePlanDisplayMiniEditor
-    else if hasQuickLook
+    DisplayComponent = if hasQuickLook
       CoursePlanDisplayQuickLook
     else
       CoursePlanDisplayEdit