If there’s a header or footer, adjust the body width to the flow component width

--- framer/Components/FlowComponent.coffee
+++ framer/Components/FlowComponent.coffee
@@ -517,6 +517,9 @@
 	bodyFrame.height = size.height - (header?.height or 0) - (footer?.height or 0)

 	body.point = 0
+	# Not scroll vertically if there's a header or footer
+	body.width = size.width
+
 	scroll = layoutScroll(body, bodyFrame)
 	scroll.parent = layer
 	scroll.frame = bodyFrame


changed conversions to work with new image magick feature pdf:fit-page=

--- app/coffee/FileConverter.coffee
+++ app/coffee/FileConverter.coffee
@@ -20,7 +20,9 @@
 		if !_.include approvedFormats, requestedFormat
 			err = new Error("invalid format requested")
 			return callback err
-		args = "nice convert -flatten -density 300 #{sourcePath} #{destPath}"
+		width = "600x"
+		args = "nice convert -define pdf:fit-page=#{width} -flatten -density 300 #{sourcePath} #{destPath}"
+		console.log args
 		exec args, childProcessOpts, (err, stdout, stderr)->
 			timer.done()
 			if err?
@@ -33,12 +35,8 @@
 		logger.log sourcePath:sourcePath, "thumbnail convert file"
 		destPath = "#{sourcePath}.png"
 		sourcePath = "#{sourcePath}[0]"
-		args =
-			src: sourcePath
-			dst: destPath
-			width: 424
-			height: 300
-		args = "nice convert -flatten -background white -resize 260x -density 300 #{sourcePath} #{destPath}"
+		width = "260x"
+		args = "nice convert -flatten -background white -density 300 -define pdf:fit-page=#{width} #{sourcePath} -resize #{width} #{destPath}"
 		exec args, childProcessOpts, (err, stdout, stderr)->
 			if err?
 				logger.err err:err, stderr:stderr, sourcePath:sourcePath, "something went wrong converting file to preview"
@@ -50,12 +48,8 @@
 		logger.log sourcePath:sourcePath, "preview convert file"
 		destPath = "#{sourcePath}.png"
 		sourcePath = "#{sourcePath}[0]"
-		args =
-			src: sourcePath
-			dst: destPath
-			width: 600
-			height: 849
-		args = "nice convert -flatten -background white -resize 548x -density 300 #{sourcePath} #{destPath}"
+		width = "548x"
+		args = "nice convert -flatten -background white -density 300 -define pdf:fit-page=#{width} #{sourcePath} -resize #{width} #{destPath}"
 		exec args, childProcessOpts, (err, stdout, stderr)->
 			if err?
 				logger.err err:err, stderr:stderr, sourcePath:sourcePath, destPath:destPath, "something went wrong converting file to preview"


hard delete a doc on health check

hard delete a doc on health check

--- app/coffee/HealthChecker.coffee
+++ app/coffee/HealthChecker.coffee
@@ -1,4 +1,4 @@
-ObjectId = require("mongojs").ObjectId
+{db, ObjectId} = require "./mongojs"
 request = require("request")
 async = require("async")
 _ = require("underscore")
@@ -40,4 +40,4 @@
 		async.series jobs, (err)->
 			if err?
 				callback(err)
-			request.del getOpts(), callback
+			db.docs.remove {_id: doc_id, project_id: project_id}, callback


FIX: Expand filter search for task and project filters

Adds tutorial abbreviation, and task names (for task filter),
to the filter search.

--- src/app/common/filters/filters.coffee
+++ src/app/common/filters/filters.coffee
@@ -165,7 +165,14 @@
   (input, text) ->
     matchText = text.toLowerCase()
     if input
-      _.filter  input, (task) -> (task?) && ( task.project().student_id.indexOf(matchText) >= 0 || task.project().name.toLowerCase().indexOf(matchText) >= 0 || task.project().tutorName().toLowerCase().indexOf(matchText) >= 0)
+      _.filter  input, (task) ->
+        if task?
+          project = task.project()
+          (task.definition.abbreviation.toLowerCase().indexOf(matchText) >= 0) ||
+            (task.definition.name.toLowerCase().indexOf(matchText) >= 0) ||
+            (project? && ( project.student_id.indexOf(matchText) >= 0 || project.name.toLowerCase().indexOf(matchText) >= 0 || (project.tutorial? && (project.tutorial.abbreviation.toLowerCase().indexOf(matchText) >= 0 || project.tutorName().toLowerCase().indexOf(matchText) >= 0))))
+        else
+          false
     else
       input
 )
@@ -174,7 +181,7 @@
   (input, text) ->
     matchText = text.toLowerCase()
     if input
-      _.filter  input, (project) -> (project?) && ( project.student_id.indexOf(matchText) >= 0 || project.name.toLowerCase().indexOf(matchText) >= 0 || project.tutorName().toLowerCase().indexOf(matchText) >= 0)
+      _.filter  input, (project) -> (project?) && ( project.student_id.indexOf(matchText) >= 0 || project.name.toLowerCase().indexOf(matchText) >= 0 || (project.tutorial? && (project.tutorial.abbreviation.toLowerCase().indexOf(matchText) >= 0 || project.tutorName().toLowerCase().indexOf(matchText) >= 0)) )
     else
       input
 )


Footer on classify page

Footer on classify page

--- app/controllers/classify.coffee
+++ app/controllers/classify.coffee
@@ -8,6 +8,7 @@
 User = require 'zooniverse/models/user'
 Subject = require 'zooniverse/models/subject'
 Classification = require 'zooniverse/models/classification'
+Footer = require 'zooniverse/controllers/footer'

 EllipseTool.Controls = DefaultControls
 CircleTool.Controls = DefaultControls
@@ -53,6 +54,9 @@
     @subjectImage = @surface.addShape 'image'

     @subjectContainer.append @surface.el
+
+    @footer = new Footer
+    @footer.el.appendTo @el

     User.on 'change', @onUserChange
     Subject.on 'getNext', @onSubjectGettingNext


fixed #156

fixed #156

--- app/front/js/config/boot.coffee
+++ app/front/js/config/boot.coffee
@@ -16,7 +16,7 @@
 # Production mode
 Application.config ['$logProvider','$compileProvider', ($logProvider, $compileProvider) ->
   # $logProvider.debugEnabled false
-  $compileProvider.debugInfoEnabled false
+  # $compileProvider.debugInfoEnabled false
 ]

 Application.run ['$rootScope', 'throbber', 'Editor', ($rootScope, throbber, Editor) ->


Fix the problem with headers sent

Fix the problem with headers sent

--- library/membrane/validate.coffee
+++ library/membrane/validate.coffee
@@ -64,9 +64,9 @@
         assert _.isObject(request), notRequest
         message = (error) -> error.message
         strings = _.map results, message
+        response.statusCode = 400
         map = _.object _.keys(results), strings
-        response.writeHead 400, "bad parameters"
-        return @push response, map
+        return @push response, errors: params: map

     # Given the request with possible validation contexts appended
     # run all the validator contexsts in parallel and wait for the


Issue #2897 - StatsTicketChannelDistribution - set channel.overal_percentage to 0 if the calculation produces no result.

--- app/assets/javascripts/app/controllers/_dashboard/stats/ticket_channel_distribution.coffee
+++ app/assets/javascripts/app/controllers/_dashboard/stats/ticket_channel_distribution.coffee
@@ -33,7 +33,7 @@
     totalChannels = _.size data.StatsTicketChannelDistribution.channels

     for id, channel of data.StatsTicketChannelDistribution.channels
-      channel.overal_percentage = Math.round((channel.inbound + channel.outbound) / totalTickets * 100)
+      channel.overal_percentage = Math.round((channel.inbound + channel.outbound) / totalTickets * 100) || 0

     data.StatsTicketChannelDistribution.description =  'How many of your tickets are coming from email, phone, Twitter, or Facebook? (Shows percentages for both inbound and outbound tickets.)'


share server: fix handling of symblinks to directories

--- src/smc-hub/share/render-public-path.cjsx
+++ src/smc-hub/share/render-public-path.cjsx
@@ -38,73 +38,80 @@
         hidden : false
         sort   : required   # e.g., '-mtime' = sort files in reverse by timestamp

-        locals =
-            path_to_file: os_path.join(opts.dir, opts.path)
-        fs.lstat locals.path_to_file, (err, stats) ->
+    locals =
+        path_to_file: os_path.join(opts.dir, opts.path)
+
+    dbg = (args...) ->
+        console.log("render_public_path('#{locals.path_to_file}')", args...)
+    dbg()
+    fs.stat locals.path_to_file, (err, stats) ->
+        if err
+            dbg("error", err)
+            opts.res.sendStatus(404)
+            return
+
+        if stats.isDirectory()
+            dbg("is directory")
+            if opts.path.slice(-1) != '/'
+                util.redirect_to_directory(opts.req, opts.res)
+                return
+
+            get_listing locals.path_to_file, (err, files) ->
+                if err
+                    # TODO: show directory listing
+                    opts.res.send("Error getting directory listing -- #{err}")
+                else
+                    if opts.sort[0] == '-'
+                        reverse = true
+                        sort = opts.sort.slice(1)
+                    else
+                        reverse = false
+                        sort = opts.sort
+                    files.sort(misc.field_cmp(sort))
+                    if reverse
+                        files.reverse()
+                    C = <DirectoryListing
+                            hidden = {opts.hidden}
+                            info   = {opts.info}
+                            files  = {files}
+                            viewer = {opts.viewer}
+                            path   = {opts.path} />
+                    opts.react(opts.res, C, opts.path)
+            return
+
+        dbg("is file")
+        # stats.size
+        # TODO: if too big... just show an error and direct raw download link
+        get_content = (cb) ->
+            if stats.size > MAX_SIZE
+                locals.content = undefined  # means -- too big to load.
+                cb()
+                return
+
+            ext = misc.filename_extension(locals.path_to_file)?.toLowerCase()
+            if extensions.image[ext] or extensions.pdf[ext] or extensions.video[ext]
+                cb()
+                return
+            else
+                fs.readFile locals.path_to_file, (err, data) ->
+                    if err
+                        cb(err)
+                    else
+                        locals.content = data.toString()
+                        cb()
+
+        get_content (err) ->
             if err
                 opts.res.sendStatus(404)
                 return

-            if stats.isDirectory()
-                if opts.path.slice(-1) != '/'
-                    util.redirect_to_directory(opts.req, opts.res)
-                    return
+            component = <PublicPath
+                info     = {opts.info}
+                content  = {locals.content}
+                viewer   = {opts.viewer}
+                path     = {opts.path}
+                size     = {stats.size}
+                max_size = {MAX_SIZE}
+                />
+            opts.react(opts.res, component, opts.path)

-                get_listing locals.path_to_file, (err, files) ->
-                    if err
-                        # TODO: show directory listing
-                        opts.res.send("Error getting directory listing -- #{err}")
-                    else
-                        if opts.sort[0] == '-'
-                            reverse = true
-                            sort = opts.sort.slice(1)
-                        else
-                            reverse = false
-                            sort = opts.sort
-                        files.sort(misc.field_cmp(sort))
-                        if reverse
-                            files.reverse()
-                        C = <DirectoryListing
-                                hidden = {opts.hidden}
-                                info   = {opts.info}
-                                files  = {files}
-                                viewer = {opts.viewer}
-                                path   = {opts.path} />
-                        opts.react(opts.res, C, opts.path)
-                return
-
-            # stats.size
-            # TODO: if too big... just show an error and direct raw download link
-            get_content = (cb) ->
-                if stats.size > MAX_SIZE
-                    locals.content = undefined  # means -- too big to load.
-                    cb()
-                    return
-
-                ext = misc.filename_extension(locals.path_to_file)?.toLowerCase()
-                if extensions.image[ext] or extensions.pdf[ext] or extensions.video[ext]
-                    cb()
-                    return
-                else
-                    fs.readFile locals.path_to_file, (err, data) ->
-                        if err
-                            cb(err)
-                        else
-                            locals.content = data.toString()
-                            cb()
-
-            get_content (err) ->
-                if err
-                    opts.res.sendStatus(404)
-                    return
-
-                component = <PublicPath
-                    info     = {opts.info}
-                    content  = {locals.content}
-                    viewer   = {opts.viewer}
-                    path     = {opts.path}
-                    size     = {stats.size}
-                    max_size = {MAX_SIZE}
-                    />
-                opts.react(opts.res, component, opts.path)
-


Simplify check for when we are editing lines with metadata commands

--- services/web/public/coffee/ide/editor/directives/aceEditor/metadata/MetadataManager.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor/metadata/MetadataManager.coffee
@@ -18,41 +18,15 @@
 					return
 				if change.action not in ['remove', 'insert']
 					return
-				cursorPosition = @editor.getCursorPosition()
+
 				end = change.end
-				range = new Range(end.row, 0, end.row, end.column)
-				lineUpToCursor = @editor.getSession().getTextRange range
-				if lineUpToCursor.trim() == '%' or lineUpToCursor.slice(0, 1) == '\\'
-					range = new Range(end.row, 0, end.row, end.column + 80)
-					lineUpToCursor = @editor.getSession().getTextRange range
-				commandFragment = getLastCommandFragment lineUpToCursor
+				lineText = @editor.getSession().getLine end.row

-				linesContainPackage = _.any(
-					change.lines,
-					(line) -> line.match(/^\\usepackage(?:\[.{0,80}?])?{(.{0,80}?)}/)
-				)
-				linesContainReqPackage = _.any(
-					change.lines,
-					(line) -> line.match(/^\\RequirePackage(?:\[.{0,80}?])?{(.{0,80}?)}/)
-				)
-				linesContainLabel = _.any(
-					change.lines,
-					(line) -> line.match(/\\label{(.{0,80}?)}/)
-				)
-				linesContainMeta =
-					linesContainPackage or
-					linesContainLabel or
-					linesContainReqPackage
+				# Defensive check against extremely long lines
+				return if lineText.length > 10000

-				lastCommandFragmentIsLabel = commandFragment?.slice(0, 7) == '\\label{'
-				lastCommandFragmentIsPackage = commandFragment?.slice(0, 11) == '\\usepackage'
-				lastCommandFragmentIsReqPack = commandFragment?.slice(0, 15) == '\\RequirePackage'
-				lastCommandFragmentIsMeta =
-					lastCommandFragmentIsPackage or
-					lastCommandFragmentIsLabel or
-					lastCommandFragmentIsReqPack
-
-				if linesContainMeta or lastCommandFragmentIsMeta
+				# Check if edited line contains metadata commands
+				if /\\(usepackage|RequirePackage|label)(\[.*])?({.*})?/.test(lineText)
 					@scheduleLoadCurrentDocMetaFromServer()

 			@editor.on "changeSession", (e) =>


Test remaining failure cases.

Test remaining failure cases.

--- services/web/test/UnitTests/coffee/ReferencesSearch/ReferencesSearchControllerTests.coffee
+++ services/web/test/UnitTests/coffee/ReferencesSearch/ReferencesSearchControllerTests.coffee
@@ -77,13 +77,14 @@
 			beforeEach ->
 				@req.body = {docId: 'some_weird_id'}
 				@ProjectLocator.findElement.callsArgWith(1, new Error('not found'), null)
+				@ReferencesSearchHandler.indexFile.callsArgWith(2, null)

 			it 'should call ProjectLocator.findElement', (done) ->
 				@res.send = (status) =>
 					@ProjectLocator.findElement.calledOnce.should.equal true
 					arg =
 						project_id: @project_id
-						element_id: @doc_id,
+						element_id: 'some_weird_id',
 						type: 'doc'
 					@ProjectLocator.findElement.calledWith(arg).should.equal true
 					done()
@@ -94,3 +95,24 @@
 					status.should.equal 500
 					done()
 				@controller.indexFile(@req, @res)
+
+		describe 'when the ReferencesSearchHandler produces an error', ->
+
+			beforeEach ->
+				@req.body = {docId: @doc_id}
+				@ProjectLocator.findElement.callsArgWith(1, null, {})
+				@ReferencesSearchHandler.indexFile.callsArgWith(2, new Error('something went wrong'))
+
+			it 'should call ReferencesSearchHandler.indexFile', (done) ->
+				@res.send = (status) =>
+					@ReferencesSearchHandler.indexFile.calledOnce.should.equal true
+					expected_url = "http://some.url/project/2222/doc/3333"
+					@ReferencesSearchHandler.indexFile.calledWith(@project_id, expected_url).should.equal true
+					done()
+				@controller.indexFile(@req, @res)
+
+			it 'should produce a 500 response', (done) ->
+				@res.send = (status) =>
+					status.should.equal 500
+					done()
+				@controller.indexFile(@req, @res)


add empty line between headers and body

add empty line between headers and body

--- src/dockerhub.coffee
+++ src/dockerhub.coffee
@@ -120,7 +120,7 @@
             msg = "#{post_res.statusCode}\n"
             for k,v of post_res.headers
               msg += "#{k}: #{v}\n"
-            msg += "#{post_body}\n"
+            msg += "\n#{post_body}\n"
             res.send msg
     else
       res.send "#{repo} のトークンが設定されていません"


Remove console.log

Remove console.log

--- src/droppable.coffee
+++ src/droppable.coffee
@@ -70,7 +70,6 @@
     # HACK some UA fires drag leave too often, we need to make sure it’s
     # actually outside of the element. There is probably better ways to check
     # this as it’s covering every case.
-    console.log("e: ", e.originalEvent.clientX, e)
     if cursorInsideElement(e.originalEvent, e.currentTarget)
       $(e.currentTarget).removeClass(@options.hoverClass) if @options.hoverClass
       @options.out?(e, e.currentTarget, typesForDataTransfer(dataTransfer))


Creates WatchedApp.List controller

Creates WatchedApp.List controller

Filters and generates new collection for movies
where watched: true

--- app/assets/javascripts/backbone/apps/watched/list/list_controller.js.coffee
+++ app/assets/javascripts/backbone/apps/watched/list/list_controller.js.coffee
@@ -2,5 +2,18 @@

   List.Controller =
     listWatched: ->
-      watchedList = App.request "watched:model"
+      App.request "movie:model", (movies) =>
+        movieList = movies.models

+        watchedArray = movies.where(watched: true)
+
+        watchedMovies = new List.WatchedMovies();
+        watchedMovies.reset(watchedArray)
+
+        watchedListView = @getWatchedListView watchedMovies
+        App.mainRegion.show watchedListView
+
+    getWatchedListView: (movies) ->
+        new List.WatchedCollection
+          collection: movies
+


#1089 Fix NML Upload

#1089 Fix NML Upload

--- app/assets/javascripts/dashboard/views/explorative_tracing_list_view.coffee
+++ app/assets/javascripts/dashboard/views/explorative_tracing_list_view.coffee
@@ -21,11 +21,11 @@
           enctype="multipart/form-data"
           id="upload-and-explore-form"
           class="form-inline inline-block">
-

+

-
-
-
+
+
+
                 Upload NML & explore

               <input type="file" name="nmlFile" multiple accept=".nml">
@@ -74,15 +74,17 @@
     parent : null

   events :
-    "change input[type=file]" : "selectFiles"
+    "change.bs.fileinput .fileinput" : "selectFiles"
     "submit @ui.uploadAndExploreForm" : "uploadFiles"
     "click @ui.toggleViewArchivedButton" : "fetchArchivedAnnotations"
     "click @ui.toggleViewOpenButton" : "fetchOpenAnnotations"
     "click @ui.archiveAllButton" : "archiveAll"

   ui :
+    fileinput : "#fileinput"
     tracingChooser : "#tracing-chooser"
     uploadAndExploreForm : "#upload-and-explore-form"
+    uploadFileInput : "#upload-and-explore-form input[type=file]"
     formSpinnerIcon : "#form-spinner-icon"
     formUploadIcon : "#form-upload-icon"
     toggleViewArchivedButton : "#toggle-view-archived"
@@ -110,7 +112,7 @@

   selectFiles : (event) ->

-    if event.target.files.length
+    if @ui.uploadFileInput[0].files.length
       @ui.uploadAndExploreForm.submit()

@@ -118,26 +120,17 @@

     event.preventDefault()

-    toggleIcon = (state) =>
-
-      @ui.formSpinnerIcon.toggleClass("hide", state)
-      @ui.formUploadIcon.toggleClass("hide", !state)
-
-
-    toggleIcon(false)
-
     form = @ui.uploadAndExploreForm

-    Request.always(
-      Request.sendMultipartFormReceiveJSON(
-        form.attr("action")
-        data : new FormData(form[0])
-      ).then((data) ->
+    Request.sendMultipartFormReceiveJSON(
+      form.attr("action")
+      data : new FormData(form[0])
+    ).then(
+      (data) ->
         url = "/annotations/" + data.annotation.typ + "/" + data.annotation.id
         app.router.loadURL(url)
         Toast.message(data.messages)
-      )
-      -> toggleIcon(true)
+      => @ui.fileinput.fileinput("clear")
     )


sort invitations by requestedAt, better would be by another field resolvedAt or modifiedAt for the resolved ones

--- client/app/Applications/Groups.kdapplication/Views/groupsrequestview.coffee
+++ client/app/Applications/Groups.kdapplication/Views/groupsrequestview.coffee
@@ -33,6 +33,7 @@
       targetOptions :
         selector    : targetSelector
         limit       : @requestLimit
+        sort        : { requestedAt: -1 }
       options       :
         sort        : { timestamp: -1 }


Activvity: add defensive check for prefetchedfeed

--- client/landing/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Activity.kdapplication/AppController.coffee
@@ -206,7 +206,7 @@
   fetchPublicActivities:(options = {})->
     {CStatusActivity} = KD.remote.api

-    if @getFeedFilter() is "Public" and @getActivityFilter() is "Everything"
+    if @getFeedFilter() is "Public" and @getActivityFilter() is "Everything" and KD.prefetchedFeeds
       prefetchedActivity = KD.prefetchedFeeds["activity.main"]
       if prefetchedActivity and prefetchedActivity.activities and ('activities.main' not in USEDFEEDS)
         log "exhausting feed:", "activity.main"


chrome app webterm improvement

chrome app webterm improvement

--- client/landing/app/Applications/WebTerm.kdapplication/webtermappview.coffee
+++ client/landing/app/Applications/WebTerm.kdapplication/webtermappview.coffee
@@ -78,6 +78,10 @@
         @showApprovalModal remote, command

       if query.fullscreen
+        windowController = KD.getSingleton("windowController")
+        windowController.unloadListeners = []
+        windowController.on "clientIdChanged", =>
+          window.parent.postMessage "clientIdChanged", "*"
         KD.getSingleton("mainView").enableFullscreen()
         if window.parent?.postMessage
           window.parent.postMessage "fullScreenTerminalReady", "*"


Replace obsolete context functions according to latest eventric changes

--- src/command_query_factory.spec.coffee
+++ src/command_query_factory.spec.coffee
@@ -56,7 +56,7 @@
       queryStub = sandbox.stub().returns new Promise (resolve) -> resolve {}
       queryParams = {}
       exampleContext = eventric.context 'Example'
-      exampleContext.addQueryHandler 'getSomething', queryStub
+      exampleContext.addQueryHandlers getSomething: queryStub
       exampleContext.initialize()
       .then ->
         commandQueryFactory.waitUntilQueryIsReady exampleContext, 'getSomething', queryParams
@@ -67,7 +67,7 @@
     it 'should reject with an error given a query which rejects with an error', ->
       error = new Error
       exampleContext = eventric.context 'Example'
-      exampleContext.addQueryHandler 'getSomething', ->
+      exampleContext.addQueryHandlers getSomething: ->
         new Promise (resolve, reject) -> reject error
       exampleContext.initialize()
       .then ->
@@ -79,7 +79,7 @@
     it 'should resolve with the result given a query callback which resolves with a result', ->
       queryResult = {}
       exampleContext = eventric.context 'Example'
-      exampleContext.addQueryHandler 'getSomething', ->
+      exampleContext.addQueryHandlers getSomething: ->
         new Promise (resolve) -> resolve queryResult
       exampleContext.initialize()
       .then ->
@@ -91,7 +91,7 @@
     it 'should execute the query repeatedly given a query which resolves but not with immediately with a result', ->
       exampleContext = eventric.context 'Example'
       callCount = 0
-      exampleContext.addQueryHandler 'getSomething', ->
+      exampleContext.addQueryHandlers getSomething: ->
         new Promise (resolve, reject) ->
           callCount++
           if callCount < 5
@@ -107,7 +107,7 @@

     it 'should reject with a descriptive error given a query which does not yield a result within the timeout', ->
       exampleContext = eventric.context 'Example'
-      exampleContext.addQueryHandler 'getSomething', ->
+      exampleContext.addQueryHandlers getSomething: ->
         new Promise (resolve) ->
           setTimeout ->
             resolve()
@@ -128,7 +128,7 @@
       commandStub = sandbox.stub().returns new Promise (resolve) -> resolve()
       commandParams = {}
       exampleContext = eventric.context 'Example'
-      exampleContext.addCommandHandler 'DoSomething', commandStub
+      exampleContext.addCommandHandlers DoSomething: commandStub
       exampleContext.initialize()
       .then ->
         commandQueryFactory.waitUntilCommandResolves exampleContext, 'DoSomething', commandParams
@@ -139,7 +139,7 @@
     it 'should resolve with the result given a command which resolves with a result', ->
       commandResult = {}
       exampleContext = eventric.context 'Example'
-      exampleContext.addCommandHandler 'DoSomething', ->
+      exampleContext.addCommandHandlers DoSomething: ->
         new Promise (resolve) -> resolve commandResult
       exampleContext.initialize()
       .then ->
@@ -151,7 +151,7 @@
     it 'should execute the command repeatedly given a command which first rejects and resolves after a while', ->
       exampleContext = eventric.context 'Example'
       callCount = 0
-      exampleContext.addCommandHandler 'DoSomething', ->
+      exampleContext.addCommandHandlers DoSomething: ->
         new Promise (resolve, reject) ->
           callCount++
           if callCount >= 5
@@ -167,7 +167,7 @@

     it 'should reject with a descriptive error given a command which does not yield a result within the timeout', ->
       exampleContext = eventric.context 'Example'
-      exampleContext.addCommandHandler 'DoSomething', ->
+      exampleContext.addCommandHandlers DoSomething: ->
         new Promise (resolve) ->
           setTimeout ->
             resolve()


Changed container formatting for management area.

--- web/coffee/management.coffee
+++ web/coffee/management.coffee
@@ -9,10 +9,8 @@

   componentDidMount: ->
     # Formatting hack
-    $("#main-content").prepend """
-

-    """
-    $("#main-content>.container").addClass("col-md-10")
+    $("#main-content>.container").addClass("container-fluid")
+    $("#main-content>.container").removeClass("container")

     apiCall "GET", "/api/admin/problems"
     .done ((api) ->


simple test

simple test

--- jasmine-harness.coffee
+++ jasmine-harness.coffee
@@ -11,6 +11,11 @@
 require.load './PerformanceTest'
 ###

+describe 'Basic Suite', ->
+  it 'Should pass a basic truthiness test.', ->
+    expect(true).toEqual(true)
+    expect(false).toEqual(false)
+
 startJasmine = ->
   jasmine.getEnv().addReporter new jasmine.TapReporter()
   jasmine.getEnv().execute()


attach every proproty jasmine to window

attach every proproty jasmine to window

--- jasmine-harness.coffee
+++ jasmine-harness.coffee
@@ -1,4 +1,8 @@
-window.jasmine = require('./spec/lib/jasmine.js').jasmine
+runner = require('./spec/lib/jasmine.js')
+
+# attach jasmine to window
+window[key] = runner[key] for key of runner
+
 html = require './spec/lib/jasmine-html.js'
 tap = require './spec/lib/jasmine.tap_reporter.js'


Improved GenericIndex widget.

Improved GenericIndex widget.

--- app/assets/javascripts/app/controllers/_application_controller_generic.js.coffee
+++ app/assets/javascripts/app/controllers/_application_controller_generic.js.coffee
@@ -112,7 +112,8 @@
     @navupdate @pageData.navupdate

     # bind render after a change is done
-    @subscribeId = App[ @genericObject ].subscribe(@render)
+    if !@disableRender
+      @subscribeId = App[ @genericObject ].subscribe(@render)

     App[ @genericObject ].bind 'ajaxError', (rec, msg) =>
       @log 'error', 'ajax', msg.status
@@ -126,10 +127,12 @@
     @render()

     # fetch all
-    App[ @genericObject ].fetch()
+    if !@disableInitFetch
+      App[ @genericObject ].fetch()

   release: =>
-    App[ @genericObject ].unsubscribe(@subscribeId)
+    if @subscribeId
+      App[ @genericObject ].unsubscribe(@subscribeId)

   render: =>


#390 Fix Shader to show correct volume colors

--- app/assets/javascripts/oxalis/geometries/materials/plane_material_factory.coffee
+++ app/assets/javascripts/oxalis/geometries/materials/plane_material_factory.coffee
@@ -160,7 +160,7 @@
         /* Color map (<= to fight rounding mistakes) */

         if ( id > 0.1 ) {
-          vec4 HSV = vec4( mod( 6.0 * id * golden_ratio, 6.0), 1.0, 1.0, 1.0 );
+          vec4 HSV = vec4( mod( id * golden_ratio, 1.0), 1.0, 1.0, 1.0 );
           gl_FragColor = vec4(mix( data_color, hsv_to_rgb(HSV), alpha ), 1.0);
         } else {
           gl_FragColor = vec4(data_color, 1.0);


setter: renderComments #347

setter: renderComments #347

--- app/assets/javascripts/oxalis/view/skeletontracing/abstract_tree_renderer.coffee
+++ app/assets/javascripts/oxalis/view/skeletontracing/abstract_tree_renderer.coffee
@@ -27,6 +27,10 @@
     @width = width
     @height = height
     @nodeList = []
+
+  renderComments : (renderComments) ->
+
+    @RENDER_COMMENTS = renderComments

   setDimensions : ({width, height}) ->


changed modal width

changed modal width

--- client/landing/app/Applications/Account.kdapplication/account/accKiteList.coffee
+++ client/landing/app/Applications/Account.kdapplication/account/accKiteList.coffee
@@ -40,7 +40,7 @@
       list.addItem itemData, null, {type : "slideDown", duration : 100}
       new KDModalView
         title   : "New Kite Information"
-        width   : 550
+        width   : 580
         overlay : yes
         content : """
                   <div class='modalformline'>


Search IA/OpenLibrary for matching industry identifiers (see #3)

--- js/book-aligner.coffee
+++ js/book-aligner.coffee
@@ -43,6 +43,7 @@
           console.log "Retrying Fusion Tables query: #{query}"
           fusion_tables_query(query, callback, error_callback)
         success: (data) ->
+          console.log "Fusion Tables result for #{query}:"
           console.log data
           if callback?
             callback(data)
@@ -184,11 +185,11 @@
               else
                 industry_identifier_query(identifier_type_mapping[identifier_type], data.metadata[identifier_type])

-process_ia = (identifier_string) ->
+process_ia = (identifier_string, score = 100) ->
   console.log 'process_ia'
   match = identifier_string.match(IA_REGEX)
   ia_id = match[2].split('&')[0]
-  process_ia_id(ia_id, 100)
+  process_ia_id(ia_id, score)
   ia_query(ia_id)

 ia_query = (ia_id, level = 0) ->
@@ -243,6 +244,24 @@
         console.log "#{identifier_type} query #{identifier} HathiTrust result:"
         console.log data
         ht_biblio_query(item.htid) for item in _.values(data)[0].items
+    $.ajax "https://openlibrary.org/api/books?bibkeys=#{identifier_type}:#{identifier}&format=json&jscmd=data",
+      type: 'GET'
+      cache: true
+      dataType: 'json'
+      crossDomain: true
+      error: (jqXHR, textStatus, errorThrown) ->
+        $('#results').append($('<div/>',{class: 'alert alert-danger', role: 'alert'}).text('Error in Internet Archive AJAX call.'))
+        console.log errorThrown
+        console.log "AJAX Error: #{textStatus}"
+      success: (data) ->
+        console.log "#{identifier_type} query #{identifier} Internet Archive result:"
+        console.log data
+        if data? and !($.isEmptyObject(data))
+          ebooks = _.values(data)[0].ebooks
+          if ebooks? and (ebooks.length > 0)
+            for ebook in ebooks
+              if ebook.preview_url.match(IA_REGEX)
+                process_ia(ebook.preview_url)

 gb_biblio_query = (gb_id, score = 0) ->
   if $("##{html_id(gb_id)}").length == 0


Simplify logic for deciding whether to re-scan doc on not

--- services/web/public/coffee/ide/editor/directives/aceEditor/labels/LabelsManager.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor/labels/LabelsManager.coffee
@@ -23,20 +23,12 @@
 				range = new Range(end.row, 0, end.row, end.column)
 				lineUpToCursor = @editor.getSession().getTextRange(range)
 				commandFragment = getLastCommandFragment(lineUpToCursor)
-				if change.action == 'remove'
-					if _.any(change.lines, (line) -> line.match(/\\label\{[^\}\n\\]{0,80}\}/))
-						@scheduleLoadLabelsFromOpenDoc()
-					if commandFragment? and commandFragment.length > 2
-						if commandFragment.startsWith('\\label{')
-							@scheduleLoadLabelsFromOpenDoc()
 				if (
-					change.action == "insert" and
-					end.row == cursorPosition.row and
-					end.column == cursorPosition.column + 1
+					change.action in ['remove', 'insert'] and
+					((_.any(change.lines, (line) -> line.match(/\\label\{[^\}\n\\]{0,80}\}/))) or
+					 (commandFragment?.length > 2 and commandFragment.startsWith('\\label{')))
 				)
-					if commandFragment? and commandFragment.length > 2
-						if commandFragment.startsWith('\\label{')
-							@scheduleLoadLabelsFromOpenDoc()
+					@scheduleLoadLabelsFromOpenDoc()

 			@editor.on "changeSession", (e) =>
 				e.oldSession.off "change", onChange


Show checkboxes

Show checkboxes

--- src/add/cb.coffee
+++ src/add/cb.coffee
@@ -11,13 +11,18 @@
     cellspacing: 0
     ->
       thead ->
-        th x for x in '№,Пользователь,Имя,Таб. №,Должность,Код,Подразделение'.split ','
+        for x in '№,Пользователь,Имя,Таб. №,Должность,Код,Подразделение'.
+            split ','
+          th x
       tbody ->
         for u, i in @
           tr
             class: if i & 1 then 'odd' else 'even'
             ->
-              td align: 'right', i+1
+              td align: 'right', nowrap: 'true', ->
+                label i+1, ' ', -> input
+                  type: 'checkbox'
+                  disabled: !u.Depts.length
               td ->
                 if u.AD
                   a
@@ -42,6 +47,9 @@
                         value: z.Kod
                         z.NameAn
                     text u.Depts.length
+      tfoot ->
+        td align: 'right', -> label '* ', -> input type: 'checkbox'
+        td colspan: 6, 'Все найденные'

 interior.innerHTML = t users
 do require './opener'


VM snapshots require operate permission on all SRs. (fix vatesfr/xo-web#429).

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -14,13 +14,38 @@
 {coroutine: $coroutine} = require 'bluebird'
 {format} = require 'json-rpc-peer'

-{JsonRpcError} = require('../api-errors')
+{
+  JsonRpcError,
+  Unauthorized
+} = require('../api-errors')
 {
   formatXml: $js2xml,
   parseXml,
   pFinally
 } = require '../utils'
 {isVmRunning: $isVMRunning} = require('../xapi')
+
+#=====================================================================
+
+checkPermissionsForSnapshot = (vm) -> (
+  permissions = []
+  $forEach(vm.$VBDs, (vbdId) =>
+    vbd = @getObject(vbdId, 'VBD')
+    vdiId = vbd.VDI
+
+    if vbd.is_cd_drive or not vdiId
+      return
+
+    permissions.push([
+      @getObject(vdiId, 'VDI').$SR,
+      'operate'
+    ])
+  )
+
+  return @hasPermissions(@session.get('user_id'), permissions).then((success) => (
+    throw new Unauthorized() unless success
+  ))
+)

 #=====================================================================

@@ -408,6 +433,8 @@
 #---------------------------------------------------------------------

 snapshot = $coroutine ({vm, name}) ->
+  yield checkPermissionsForSnapshot.call(this, vm)
+
   snapshot = yield @getXAPI(vm).snapshotVm(vm.ref, name)
   return snapshot.$id

@@ -424,6 +451,8 @@
 #---------------------------------------------------------------------

 rollingSnapshot = $coroutine ({vm, tag, depth}) ->
+  yield checkPermissionsForSnapshot.call(this, vm)
+
   snapshot = yield @getXAPI(vm).rollingSnapshotVm(vm.ref, tag, depth)
   return snapshot.$id

@@ -623,6 +652,9 @@

 # TODO: integrate in xapi.js
 export_ = $coroutine ({vm, compress, onlyMetadata}) ->
+  if vm.power_state is 'Running'
+    yield checkPermissionsForSnapshot.call(this, vm)
+
   stream = yield @getXAPI(vm).exportVm(vm.id, {
     compress: compress ? true,
     onlyMetadata: onlyMetadata ? false


fix missing arg

fix missing arg

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -161,7 +161,7 @@

   xapi = @getXAPI VM

-  xapi.call 'VM.pool_migrate', VM.ref, host.ref
+  xapi.call 'VM.pool_migrate', VM.ref, host.ref, {}

 exports.set = ->
   params = @getParams {


Add guard clause on variant overrides filter in case hub id is not present in the hubPermissions

--- app/assets/javascripts/admin/variant_overrides/filters/hub_permissions_filter.js.coffee
+++ app/assets/javascripts/admin/variant_overrides/filters/hub_permissions_filter.js.coffee
@@ -1,4 +1,5 @@
 angular.module("admin.variantOverrides").filter "hubPermissions", ($filter) ->
   return (products, hubPermissions, hub_id) ->
     return [] if !hub_id
+    return [] if !hubPermissions[hub_id]
     return $filter('filter')(products, ((product) -> hubPermissions[hub_id].indexOf(product.producer_id) > -1), true)


#1281 Fix set abstract tree viewer active node

--- app/assets/javascripts/oxalis/view/skeletontracing/right-menu/abstract_tree_view.coffee
+++ app/assets/javascripts/oxalis/view/skeletontracing/right-menu/abstract_tree_view.coffee
@@ -65,7 +65,7 @@

     id = @abstractTreeRenderer.getIdFromPos(event.offsetX, event.offsetY)
     if id
-      @model.skeletonTracing.trigger("newActiveNode", id)
+      @model.skeletonTracing.setActiveNode(id)
       @model.skeletonTracing.centerActiveNode()


forgot to start with 0

forgot to start with 0

--- cf/spell.coffee
+++ cf/spell.coffee
@@ -25,7 +25,7 @@
     @current = data['words'][choice]['name']
     [1..@current.length].forEach ->
       @reveal = @reveal.concat("_")
-    @unfinished = [1..@current.length - 1]
+    @unfinished = [0..@current.length - 1]
     return data['words'][choice]

   update_question = (data) ->
@@ -46,7 +46,7 @@
     n = @unfinished[decide]
     @reveal = @reveal.substring(0,n) + @current[n] + @reveal.substring(n + 1)
     @unfinished.splice(decide,1)
-
+
   update_keyHandler = () ->
     $("#input_answer").off()
     $("#input_answer").keydown((e) =>


remove disabled period/date inputs when assignment is open

--- src/components/task-plan/builder.cjsx
+++ src/components/task-plan/builder.cjsx
@@ -195,6 +195,8 @@
       </BS.Row>

     feedbackNote = '  Feedback will be released after the due date.' if plan.type is 'homework'
+    cannotEditNote = '  Open times cannot be editted after assignment is visible to students.' if @state.isVisibleToStudents
+

     assignmentNameLabel = [
       'Assignment name'
@@ -233,41 +235,35 @@
             Open time is 12:01am.
             Set date to today to open immediately.
             Due time is 7:00am.
+            {cannotEditNote}
             {feedbackNote}

         </BS.Col>
       </BS.Row>

-      <BS.Row className="tutor-date-input">
-        <BS.Col sm=4 md=3>
-          <input
-            id='hide-periods-radio'
-            name='toggle-periods-radio'
-            ref='allPeriodsRadio'
-            type='radio'
-            onChange={@setAllPeriods}
-            disabled={@state.isVisibleToStudents}
-            checked={not @state.showingPeriods}/>
-          All Periods
-        </BS.Col>
-        {@renderCommonDateInputs() unless @state.showingPeriods}
-      </BS.Row>
-
-      <BS.Row>
-        <BS.Col md=12>
-          <input
-            id='show-periods-radio'
-            name='toggle-periods-radio'
-            type='radio'
-            onChange={@setIndividualPeriods}
-            disabled={@state.isVisibleToStudents}
-            checked={@state.showingPeriods}/>
-          Individual Periods
-        </BS.Col>
-      </BS.Row>
-      { _.map(CourseStore.get(@props.courseId)?.periods, @renderTaskPlanRow) if @state.showingPeriods }
+      {@renderCommonChoice() unless @state.isVisibleToStudents and @state.showingPeriods}
+      {@renderPeriodsChoice() unless @state.isVisibleToStudents and not @state.showingPeriods}
       { invalidPeriodsAlert }

+
+
+  renderCommonChoice: ->
+    radio = <input
+      id='hide-periods-radio'
+      name='toggle-periods-radio'
+      ref='allPeriodsRadio'
+      type='radio'
+      onChange={@setAllPeriods}
+      disabled={@state.isVisibleToStudents}
+      checked={not @state.showingPeriods}/> unless @state.isVisibleToStudents
+
+    <BS.Row className="tutor-date-input">
+      <BS.Col sm=4 md=3>
+        {radio}
+        All Periods
+      </BS.Col>
+      {@renderCommonDateInputs() unless @state.showingPeriods}
+    </BS.Row>

   renderCommonDateInputs: ->
     commonDueAt = TaskPlanStore.getDueAt(@props.id)
@@ -305,12 +301,34 @@
       dueAt
     ]

+  renderPeriodsChoice: ->
+    radio = <input
+      id='show-periods-radio'
+      name='toggle-periods-radio'
+      type='radio'
+      onChange={@setIndividualPeriods}
+      disabled={@state.isVisibleToStudents}
+      checked={@state.showingPeriods}/> unless @state.isVisibleToStudents
+
+    choiceLabel = <BS.Row>
+      <BS.Col md=12>
+        {radio}
+        Individual Periods
+      </BS.Col>
+    </BS.Row>
+
+    periodsChoice = _.map(CourseStore.get(@props.courseId)?.periods, @renderTaskPlanRow) if @state.showingPeriods
+    periodsChoice ?= []
+    periodsChoice.unshift(choiceLabel)
+    periodsChoice
+
+
   renderTaskPlanRow: (plan) ->
     # newAndUnchanged = TaskPlanStore.isNew(@props.id) and not store.isChanged(@props.id)
     # if TaskPlanStore.hasTasking(@props.id, plan.id) or newAndUnchanged
     if TaskPlanStore.hasTasking(@props.id, plan.id)
       @renderEnabledTasking(plan)
-    else
+    else unless @state.isVisibleToStudents
       @renderDisabledTasking(plan)

   renderDisabledTasking: (plan) ->


Update: atom configuration

Update: atom configuration

--- Users/mingelz/.atom/config.cson
+++ Users/mingelz/.atom/config.cson
@@ -14,6 +14,9 @@
     showInvisibles: true
   "exception-reporting":
     userId: "53fd9ef9-f628-4235-8535-c7c0a1ee661b"
+  "platformio-ide-terminal":
+    toggles:
+      autoClose: true
   "linter-ui-default":
     useBusySignal: false
   "vim-mode-plus":


Fix problem of double click on CollapsibleRow

--- views/main_table.cjsx
+++ views/main_table.cjsx
@@ -123,7 +123,8 @@
       @setState
         height: 0
       # Allow an extra 100ms timeout before hiding
-      setTimeout (=> @setState {hidden: true}), 350+100
+      # Check rowExpanded again in case another click happens before it
+      setTimeout (=> @setState {hidden: true} if !@props.rowExpanded), 350+100

   render: ->
     trClasses = classnames


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera.coffee
+++ src/camera.coffee
@@ -6,7 +6,7 @@
       width: App.width
       height: App.height
     screen: Rectangle
-      x: a
+      x: App.width /
       y: 0
       width: App.width
       height: App.height


adding helper to convert setter nicknames to ids and cleanup

--- database.coffee
+++ database.coffee
@@ -1,8 +1,7 @@
-
-mongoose = require('mongoose')
+mongoose = require 'mongoose'
 module.exports = mongoose

-mongoose.connect('mongodb://localhost/minimum')
+mongoose.connect 'mongodb://localhost/minimum'

 Schema = mongoose.Schema
 ObjectID = Schema.ObjectId
@@ -25,9 +24,17 @@
             else
                 callback new Error 'Setter not found'

+    nicksToSetterIDs: (setter_nicks, callback) ->
+        Setter.find { 'nickname': { $in : setter_nicks } }, (err, setters) ->
+            ids = []
+            for setter in setters
+                ids.push setter._id

-mongoose.model('setter', SetterSchema)
-Setter = mongoose.model('setter')
+            callback null, ids
+
+
+mongoose.model 'setter', SetterSchema
+Setter = mongoose.model 'setter'

 BoulderSchema = new Schema
@@ -42,7 +49,7 @@
     comments: { type : [ String ],   requried : false, default: []       },
     addedBy : { type : ObjectID,     required : false                    }

-#BoulderSchema.methods =
+
 BoulderSchema.statics =
     downgrade: (id, gradenr) ->
         Boulder.findOne { _id: id }, (err, boulder) ->
@@ -77,5 +84,5 @@
                 boulder.save()

-mongoose.model('boulder', BoulderSchema)
-Boulder = mongoose.model('boulder')
+mongoose.model 'boulder', BoulderSchema
+Boulder = mongoose.model 'boulder'


Remove no-op pong

Remove no-op pong

--- src/client.coffee
+++ src/client.coffee
@@ -128,9 +128,6 @@
         @emit 'close'
         @connected = false
         @socketUrl = null
-
-      @ws.on 'ping', (data, flags) =>
-        @ws.pong data, null, true

       return true


Handle HG:Extra lines in commit parsing

Handle HG:Extra lines in commit parsing

There is a chance that a commit has N number of HG: lines in a commit.

see: https://github.com/mozilla/addon-sdk

This will filter those lines out so parsing still works

--- src/commit.coffee
+++ src/commit.coffee
@@ -89,6 +89,12 @@
       while /^kilnhgcopies/.test lines[0]
         lines.shift()

+      # if converted from mercurial gpgsig may be present with non-valid gpg lines
+      # e.g. "HG:extra rebase_source:6c01d74dd05f50ede33608fe3f1b2049d93abbda"
+      # and  "HG:rename-source hg"
+      while /^HG:/.test lines[0]
+        lines.shift()
+
       # not doing anything with this yet, but it's sometimes there
       if /^encoding/.test lines[0]
         encoding = _.last lines.shift().split(" ")


Close unneeded channels in discovery.

Close unneeded channels in discovery.

--- src/discovery.coffee
+++ src/discovery.coffee
@@ -64,6 +64,8 @@
           @removeListener 'error', onError
           @stop()
           resolve channel
+        else
+          channel.close()
       onError = (error) =>
         @removeListener 'channel', onChannel
         @removeListener 'error', onError
@@ -98,7 +100,10 @@
     BleChannel.fromPeripheral(peripheral)
       .then (bleChannel) =>
         return if bleChannel is null
-        @emit 'channel', bleChannel
+        if @_started is true
+          @emit 'channel', bleChannel
+        else
+          bleChannel.close()
       .catch (error) =>
         @emit 'error', error

@@ -109,7 +114,7 @@
   _onSerialPortList: (error, ports) ->
     if error
       @emit 'error', error
-    return unless ports
+    return unless ports and @_started is true
     for port in ports
       continue unless rfconnPath = port.comName
       @_onSerialPort rfconnPath


Draw all grids with paths

Draw all grids with paths

--- src/draw_grid.coffee
+++ src/draw_grid.coffee
@@ -1,13 +1,9 @@
 exports.draw_grid = (S, grid_size, grid_split) ->
   cell_size = grid_size / grid_split
   draw_xgrid = (x) ->
-    S.line {
-      x1: x, y1: 1
-      x2: x, y2: grid_size-1
-      stroke: "#ffffff"
-    }
+    S.path {d: "M#{x},1 V#{grid_size-1}", stroke: "#ffffff"}
   draw_ygrid = (y) ->
-    S.path {d: "M1,#{y} L#{grid_size-1},#{y}", stroke: "#ffffff"}
+    S.path {d: "M1,#{y} H#{grid_size-1}", stroke: "#ffffff"}
   draw_leg = (x, y) ->
     size = cell_size * 0.5
     S.rect {


round padding values up to prevent flicker

--- static/coffee/renderers/guide/axis.coffee
+++ static/coffee/renderers/guide/axis.coffee
@@ -162,8 +162,6 @@
     @major_label_props.set(ctx, @)
     @_apply_location_heuristics(ctx, side, orient)

-    console.log side, nx*standoff, ny*standoff
-
     for i in [0..sx.length-1]
       if angle
         ctx.translate(sx[i]+nx*standoff, sy[i]+ny*standoff)
@@ -290,7 +288,9 @@
     if extent > 0
       extent += @mget('major_label_standoff')

-    return extent
+    rounding = @mget('rounding_value')
+
+    return (Math.floor(extent/rounding) + 1) * rounding

   _axis_label_extent: () ->
     extent = 0
@@ -314,7 +314,9 @@
       else
         extent += w*c + h*s

-    return extent
+    rounding = @mget('rounding_value')
+
+    return (Math.floor(extent/rounding) + 1) * rounding

   _padding_request: () ->
     req = {}
@@ -341,7 +343,6 @@
   initialize: (attrs, options)->
     super(attrs, options)

-    console.log "FOOOOOO"
     @register_property('bounds', @_bounds, false)
     @add_dependencies('bounds', this, ['guidespec'])
     @add_dependencies('bounds', @get_obj('plot'), ['x_range', 'y_range'])
@@ -549,6 +550,8 @@
   axis_label_text_align: "center"
   axis_label_text_baseline: "alphabetic"

+  rounding_value: 20
+
 })

 class LinearAxes extends Backbone.Collection


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/joysticks.coffee
+++ src/joysticks.coffee
@@ -38,6 +38,7 @@
     $ "<a />",
       css:
         backgroundColor: "yellow"
+        boxSizing: "border-box"
         color: "#000"
         display: "block"
         fontWeight: "bold"


Returns when URL provided is invalid

Returns when URL provided is invalid

--- src/hubot-archive-today.coffee
+++ src/hubot-archive-today.coffee
@@ -23,5 +23,9 @@
             .header("content-type", "application/x-www-form-urlencoded")
             .post(data) (err, res, body) ->
                 robot.logger.debug body
+
+                if err or body.match(/Invalid URL:/i)
+                    return msg.reply "#{msg.match[1]} is not a valid URL."
+
                 links = body.match /https:\/\/archive.today\/.{5}/i
                 msg.reply links[0]


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/expirable.coffee
+++ src/expirable.coffee
@@ -23,7 +23,7 @@
 # => false
 </pre></code>

-@name Durable
+@name Expirable
 @module
 @constructor
 @param {Object} I Instance variables


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/expirable.coffee
+++ src/expirable.coffee
@@ -8,8 +8,7 @@

   self.bind "update", ->
     if I.fadeOut
-
-      I.alpha = 1 - ((I.age + 1) / I.duration)
+      I.alpha = startingA1 - ((I.age + 1) / I.duration)

     if I.duration != -1 && I.age + 1 >= I.duration
       I.active = false


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/expirable.coffee
+++ src/expirable.coffee
@@ -8,7 +8,7 @@

   self.bind "update", ->
     if I.fadeOut
-      log (I.age + 1 /
+      log (I.age + 1 / I.duration)
       I.alpha = 1 - (I.age + 1 / I.duration)

     if I.duration != -1 && I.age + 1 >= I.duration


remove log

remove log

--- src/js/player.coffee
+++ src/js/player.coffee
@@ -165,7 +165,6 @@
             )

         retry: ->
-            console.log 'in retry', @_retryTimes, @opts.maxRetryTimes
             if @_retryTimes < @opts.maxRetryTimes
                 @_retryTimes++
                 url = @getUrl()


change boundary condition

change boundary condition

--- views/gorillas.coffee
+++ views/gorillas.coffee
@@ -258,7 +258,7 @@
     window.show_player_field('player_'+next_player, 'angle')

   within_boundaries:(x, y) ->
-    return false if x < 0 || x > @width || y > @height || y < 0
+    return false if x < 0 || x > @width || y > @height

   star:(x, y) ->
     color = '#FFFF00'


fixed pkg path

fixed pkg path

--- src/g/package.coffee
+++ src/g/package.coffee
@@ -32,6 +32,6 @@
     if @g.config.get("debug")
       url = @development[pkg]
     else
-      url = "http://wzrd.in/bundle/#{pkg}/latest"
+      url = "http://wzrd.in/bundle/#{pkg}@latest"

     url


just change asset not segmenet also

just change asset not segmenet also

--- web/asset/display.coffee
+++ web/asset/display.coffee
@@ -12,7 +12,7 @@
       $scope.asset = if 'exact' of $attrs then asset else asset.inContext()
       $scope.readable = $scope.asset.checkPermission(constants.permission.VIEW)
       if $scope.$parent.publicView == true
-        $scope.asset.segment = $scope.asset.excerpts[0].segment
+        $scope.asset = $scope.asset.excerpts[0]
       return
       if $scope.asset != asset
         $scope.clip = asset.segment.relativeTo($scope.asset.segment)


Fix method typo

Fix method typo

--- src/config.coffee
+++ src/config.coffee
@@ -865,7 +865,7 @@

     if value?
       defaultValue = @deepClone(defaultValue)
-      value = _.deepExtends(defaultValue, value) if isPlainObject(value) and isPlainObject(defaultValue)
+      value = _.deepExtend(defaultValue, value) if isPlainObject(value) and isPlainObject(defaultValue)
     else
       value = @deepClone(defaultValue)


What id to initialize the ace editor on is now passed to the PlayerCodeEditor instead of always being 'editor'

--- web/scripts/index.coffee
+++ web/scripts/index.coffee
@@ -15,13 +15,13 @@
             usedAtStart: 1
         }
     }
-    editor = new PlayerCodeEditor levelOne, commands
+    editor = new PlayerCodeEditor "editor", levelOne, commands
     window.Editor = editor # For testing only, puts editor in global namespace.
     return

 class PlayerCodeEditor
-    constructor: (@codeText, @commands) ->
-        @editor = ace.edit "editor"
+    constructor: (@editorDivId, @codeText, @commands) ->
+        @editor = ace.edit @editorDivId
         @editSession = @editor.getSession()
         @editor.setTheme "ace/theme/chrome"
         @editSession.setMode "ace/mode/java"


Remove "/v1" in API location

Remove "/v1" in API location

--- web/scripts/varda.coffee
+++ web/scripts/varda.coffee
@@ -27,7 +27,7 @@

 ## Create expanded API URI
-expand = (uri) -> '/api/v1' + uri
+expand = (uri) -> '/api' + uri

 # Our Sammy application


uses proper triggers.

uses proper triggers.

--- app/assets/javascripts/backend/budget.js.coffee
+++ app/assets/javascripts/backend/budget.js.coffee
@@ -1,17 +1,6 @@
 (($) ->
   'use strict'
   # usefull functions
-  survey = () ->
-    # triggers 'change' on interesting hidden inputs
-    $(document).on 'change click keyup', "input[data-value-parameter-name]", ->
-      name = $(this).attr('data-value-parameter-name')
-      hidden_input = $(this).siblings("input:hidden[data-parameter-name='#{name}']")
-      old = hidden_input.val()
-      setInterval () ->
-          if hidden_input.val() != old
-            old = hidden_input.val()
-            hidden_input.trigger('change')
-        , 100
   updateItems = () ->
     $(".budget_nested_fields").filter(':visible').each ->
       default_value = $(this).find("input.homogeneous").filter(':visible').first().val()
@@ -90,7 +79,6 @@
       $(this).text(revenues - expenses)

   $(document).ready ->
-    survey()
     # sorts budgets by direction
     $("tr[data-budget-direction]").each ->
       direction = $(this).attr('data-budget-direction')
@@ -175,7 +163,7 @@
   # updates working indicator measure values
   $(document).on 'change', "select[id^='production_working_']", ->
     updateIndicatorsValues()
-  $(document).on 'change', "input:hidden[data-parameter-name='storage_id']", ->
+  $(document).on 'selector:change', "input:hidden[data-parameter-name='storage_id']", ->
     updateIndicatorValue($(this))

   # manages working indicator and working unit
@@ -186,7 +174,7 @@
     $(this).parent().find("select[id$='working_unit']").val(unit)

   # manages selects to show only indicators in relation with the given variant
-  $(document).on 'change', "input:hidden[data-parameter-name='variant_id']", ->
+  $(document).on 'selector:change', "input:hidden[data-parameter-name='variant_id']", ->
     variant_id = $(this).attr('value')
     console.log(variant_id)
     indicator_select = $(this).parent().find("input[name='budget_indicator']")


FIX custom arguments with delegate and cancel_delay

When using `cancel_delay` or `delegate` in event declaration, it was not
possible anymore to pass custom parameters to event callback, like

    $el.trigger( 'change', 'my_custom_param' )

--- app/assets/javascripts/darwin/controller.coffee
+++ app/assets/javascripts/darwin/controller.coffee
@@ -66,7 +66,7 @@
         callback = ( event ) =>
           window.clearTimeout $element.data( "_#{method_name}_timeout" )
           wrapped = wrap( method, definition )
-          $element.data( "_#{method_name}_timeout", window.setTimeout( ( -> ( wrapped event ) ), definition.cancel_delay ) )
+          $element.data( "_#{method_name}_timeout", window.setTimeout( ( -> ( wrapped arguments ) ), definition.cancel_delay ) )
       else
         callback = wrap method, definition

@@ -78,7 +78,7 @@
         callback = ( event ) =>
           window.clearTimeout $element.data( "_#{method_name}_timeout" )
           wrapped = wrap( method, definition )
-          $element.data( "_#{method_name}_timeout", window.setTimeout( ( -> ( wrapped event ) ), definition.cancel_delay ) )
+          $element.data( "_#{method_name}_timeout", window.setTimeout( ( -> ( wrapped arguments ) ), definition.cancel_delay ) )
       else
         callback = wrap method, definition


#713 remove outcommented code

#713 remove outcommented code

--- app/assets/javascripts/oxalis/controller.coffee
+++ app/assets/javascripts/oxalis/controller.coffee
@@ -241,9 +241,7 @@
       event.preventDefault() if (event.which == 32 or event.which == 18 or 37 <= event.which <= 40) and !$(":focus").length
       return

-    keyboardControls = {
-      # "q" : => @toggleFullScreen()
-    }
+    keyboardControls = {}

     if @controlMode == constants.CONTROL_MODE_TRACE
       _.extend( keyboardControls, {


added change put event

added change put event

--- app/assets/javascripts/pt/tags/events.js.coffee
+++ app/assets/javascripts/pt/tags/events.js.coffee
@@ -1,7 +1,7 @@
 window.pt ||= tags: []

-for event in ['mouseover', 'mouseout']
-  for extraMethod in [false, 'find']
+for event in ['mouseover', 'mouseout', 'change']
+  for extraMethod in [false, 'find', 'put']
     eventName = if extraMethod then "#{event}-#{extraMethod}" else event
     newEvent = {}
     newEvent[eventName] =
@@ -21,6 +21,13 @@
           when 'find'
             [method, action, filter] = data
             $el.find(filter)[method](action)
+          when 'put'
+            $.ajax
+              url: data.first()
+              type: 'PUT'
+              data:
+                field: $el.attr 'name'
+                value: $el.val()
           else
             [method, action] = data
             $el[method](action)


conditionally load track sortable js only for teachers

--- app/assets/javascripts/teacher_tracks.js.coffee
+++ app/assets/javascripts/teacher_tracks.js.coffee
@@ -15,14 +15,15 @@
     # Checkpoints sort
     Curri.Checkpoint.sortable()

-  $('.tracks').sortable
-    items: "> div.grid-unit"
-    handle: '.unit-header'
-    cursor: 'move'
-    placeholder: "track-drop-highlight"
-    start: (e, ui) ->
-      ui.placeholder.height(ui.item.height())
-      ui.placeholder.width(ui.item.width())
-      $(ui.placeholder).addClass("grid-unit narrow")
-    update: ->
-      $.post($(this).data('url'), $(this).sortable('serialize'))+  if $('.tracks').length && Curri.user.classrole_type == 'Teacher'
+    $('.tracks').sortable
+      items: "> div.grid-unit"
+      handle: '.unit-header'
+      cursor: 'move'
+      placeholder: "track-drop-highlight"
+      start: (e, ui) ->
+        ui.placeholder.height(ui.item.height())
+        ui.placeholder.width(ui.item.width())
+        $(ui.placeholder).addClass("grid-unit narrow")
+      update: ->
+        $.post($(this).data('url'), $(this).sortable('serialize'))


TOC expanded by default

TOC expanded by default

--- app/assets/javascripts/tree_navigator.js.coffee
+++ app/assets/javascripts/tree_navigator.js.coffee
@@ -1,5 +1,5 @@
 App.TreeNavigator =
-  closeNodes: (nodes) ->
+  setNodes: (nodes) ->
     children = nodes.children('ul')

     if(children.length == 0)
@@ -7,9 +7,8 @@

     children.each ->
       link = $(this).prev('a')
-      $('').insertBefore(link)
-      $(this).hide()
-      App.TreeNavigator.closeNodes($(this).children())
+      $('').insertBefore(link)
+      App.TreeNavigator.setNodes($(this).children())

   initialize: ->
     elem = $('[data-tree-navigator]')
@@ -18,7 +17,7 @@

     ul = elem.find('ul:eq(0)')
     if(ul.length && ul.children().length)
-      App.TreeNavigator.closeNodes(ul.children())
+      App.TreeNavigator.setNodes(ul.children())

     $('[data-tree-navigator] span').on
       click: (e) ->


Fix bug where view element is missing in the DOM.

--- app/assets/javascripts/views/map.view.js.coffee
+++ app/assets/javascripts/views/map.view.js.coffee
@@ -71,6 +71,7 @@
         bbox: bounds.toBBoxString()

     request.then (data)->
+      Ember.run.once(that, 'closePopup')
       Ember.run.once(that, 'replaceData', data)

       that.lastLoadedBounds = bounds.pad(Wheelmap.MarkerLayer.BOUNDS_CONTAINS_BUFFER)


Slightly less stupid

Slightly less stupid

--- app/assets/javascripts/menu.coffee
+++ app/assets/javascripts/menu.coffee
@@ -4,11 +4,14 @@
   e.preventDefault()
   User.run_login false, ->
     window.location = window.location
-    return
-  return
-$(document).on 'click', '#forum-mark-all-read', ->
+
+
+$(document).on 'click', '#forum-mark-all-read', (e) ->
+  e.preventDefault()
   Forum.mark_all_read()
-  false
+
+
+
 window.Menu =
   menu: null
   set_post_moderate_count: ->


Hopefully the last instance of renaming organization

--- app/controllers/organizations_controller.coffee
+++ app/controllers/organizations_controller.coffee
@@ -1,10 +1,10 @@
 AuthenticatedController = Caboose.get('AuthenticatedController')
-Organisation = Caboose.get('Organisation')
+Organization = Caboose.get('Organization')

-class OrganisationsController extends AuthenticatedController
+class OrganizationsController extends AuthenticatedController

   index: ->
-    Organisation.where({users: @current_user._id}).array (err, orgs) =>
+    Organization.where({users: @current_user._id}).array (err, orgs) =>
       return @error(err) if err?
       @orgs = orgs
       @render()


Use HTML symbolic name rather than hex number.