Testing KanbanController

Testing KanbanController

--- test/unit/controllers/kanbanControllerSpec.coffee
+++ test/unit/controllers/kanbanControllerSpec.coffee
@@ -19,7 +19,7 @@
         default_issue_status: 1,
         default_issue_type: 1,
         default_question_status: 1,
-        members: []
+        members: [1, 2]
         tags: "",
         list_of_milestones: [
             {
@@ -145,6 +145,7 @@
             total_points: 0
             assigned_to: 2
             subject: "Create the user model"
+            tags: ["test1", "test2", "test3"]
         }
     ]
 }
@@ -161,14 +162,31 @@
         scope = null
         ctrl = null

-        beforeEach(inject(($rootScope, $controller, $httpBackend) ->
+        beforeEach(inject(($rootScope, $controller, $httpBackend, $gmFilters) ->
             scope = $rootScope.$new()
             routeParams = {
                 pslug: "test"
             }
+            gmFiltersMock = {
+                generateTagsFromUserStoriesList: ->
+                    ["test1", "test2", "test3"]
+                generatePersonFiltersFromUserStories: ->
+                    [1]
+                getSelectedFiltersList: ->
+                    ["test2"]
+                isFilterSelected: (p, n, t)->
+                    return t is "test2"
+                selectFilter: ->
+                unselectFilter: ->
+                plainTagsToObjectTags: $gmFilters.plainTagsToObjectTags
+                filterToText: $gmFilters.filterToText
+                generateFiltersForKanban: $gmFilters.generateFiltersForKanban
+                getFiltersForUserStory: $gmFilters.getFiltersForUserStory
+            }
             ctrl = $controller("KanbanController", {
                 $scope: scope,
-                $routeParams: routeParams
+                $routeParams: routeParams,
+                $gmFilters: gmFiltersMock
             })
             httpBackend = $httpBackend
             httpBackend.whenGET("#{APIURL}/sites").respond(200, {test: "test"})
@@ -192,6 +210,72 @@

         it "should set the breadcrumb", ->
             expect(ctrl.rootScope.pageBreadcrumb).to.be.lengthOf(2)
+
+        it "should allow initialize the filters", ->
+            ctrl.initializeFilters()
+            expect(ctrl.filters).to.be.deep.equal({tags: ["test1", "test2", "test3"], assignedTo: [1]})
+            expect(ctrl.selectedFilters).to.be.deep.equal(["test2"])
+
+        it "should allow to check if a filter is selected", ->
+            expect(ctrl.isFilterSelected("test2")).to.be.true
+            expect(ctrl.isFilterSelected("test6")).to.be.false
+
+        it "should allow toggle a filter", ->
+            ctrl.filterUserStories = ->
+
+            sinon.spy(ctrl.gmFilters, "selectFilter")
+            sinon.spy(ctrl.gmFilters, "unselectFilter")
+            sinon.spy(ctrl, "filterUserStories")
+
+            ctrl.selectedFilters = [
+                {type: "test1", id: "test1"},
+                {type:"test2", id: "test2"}
+            ]
+
+            ctrl.toggleFilter({type: "test", id: "test"})
+            expect(ctrl.gmFilters.selectFilter).have.been.called.once
+            expect(ctrl.selectedFilters).to.be.deep.equal([
+                {type: "test1", id: "test1"},
+                {type:"test2", id: "test2"},
+                {type: "test", id: "test"}
+            ])
+
+            ctrl.selectedFilters = [
+                {type: "test1", id: "test1"},
+                {type:"test2", id: "test2"},
+                {type: "test", id: "test"}
+            ]
+            ctrl.toggleFilter({type: "test", id: "test"})
+            expect(ctrl.gmFilters.unselectFilter).have.been.called.once
+            expect(ctrl.selectedFilters).to.be.deep.equal([
+                {type: "test1", id: "test1"},
+                {type:"test2", id: "test2"}
+            ])
+
+            expect(ctrl.filterUserStories).have.been.called.twice
+
+        it "should allow to filter uss by selected tags", ->
+            ctrl.selectedFilters = []
+            ctrl.scope.userstories = [
+                {id: 1, tags: ["test1"], assigned_to: 1},
+                {id: 2, tags: ["test2"], assigned_to: 2}
+            ]
+            ctrl.filterUserStories()
+            expect(ctrl.scope.userstories).to.be.deep.equal([
+                {id: 1, tags: ["test1"], assigned_to: 1, __hidden: false},
+                {id: 2, tags: ["test2"], assigned_to: 2, __hidden: false}
+            ])
+
+            ctrl.selectedFilters = [{type: "tags", id: "test2"}]
+            ctrl.scope.userstories = [
+                {id: 1, tags: ["test1"], assigned_to: 1},
+                {id: 2, tags: ["test2"], assigned_to: 2}
+            ]
+            ctrl.filterUserStories()
+            expect(ctrl.scope.userstories).to.be.deep.equal([
+                {id: 1, tags: ["test1"], assigned_to: 1, __hidden: true},
+                {id: 2, tags: ["test2"], assigned_to: 2, __hidden: false}
+            ])

     describe "KanbanUsModalController", ->
@@ -308,7 +392,6 @@
             httpBackend.expectPOST("#{APIURL}/userstories?", {test: "test"}).respond(400)
             promise = ctrl._submit()
             httpBackend.flush()
-            # TODO: Fix me
             promise.should.be.rejected
             promise.then ->
                 expect(ctrl.scope.formOpened).to.be.true
@@ -373,6 +456,3 @@
             ctrl.openUs(projectSlug, usRef)
             ctrl.location.url.should.have.been.calledOnce
             ctrl.location.url.should.have.been.calledWith("/project/test/user-story/1")
-
-
-


fixed indentation

fixed indentation

--- src/models/InitialView.coffee
+++ src/models/InitialView.coffee
@@ -12,10 +12,10 @@

 		steroids.nativeBridge.nativeCall
-          method: "dismissInitialView"
-          parameters: parameters
-          successCallbacks: [callbacks.onSuccess]
-          failureCallbacks: [callbacks.onFailure]
+			method: "dismissInitialView"
+			parameters: parameters
+			successCallbacks: [callbacks.onSuccess]
+			failureCallbacks: [callbacks.onFailure]

 	show: (options={}, callbacks={})->
@@ -29,8 +29,8 @@
 				transition: "fade"

 		steroids.nativeBridge.nativeCall
-		  method: "showInitialView"
-		  parameters: parameters
-		  successCallbacks: [callbacks.onSuccess]
-		  failureCallbacks: [callbacks.onFailure]
+			method: "showInitialView"
+			parameters: parameters
+			successCallbacks: [callbacks.onSuccess]
+			failureCallbacks: [callbacks.onFailure]


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/color2.coffee
+++ test/color2.coffee
@@ -195,5 +195,17 @@
   equal color2.b, 239
   equal color2.a, 1.0

+test "#complement", ->
+  # fix hue conversion rounding errors
+
+  color = Color2(10, 30, 50)
+
+  color2 = color.complement()
+
+  equal color2.r, 51
+  equal color2.g, 31
+  equal color2.b, 10
+  equal color2.a, 1.0
+
 module()


build-client: wrap json parse in try/catch block

--- build-client.coffee
+++ build-client.coffee
@@ -20,7 +20,11 @@
 buildAPI          = require 'bongo-api-builder'
 bongo             = require 'bongo'

-KONFIG = JSON.parse process.env.KONFIG_JSON or {}
+try
+  KONFIG = JSON.parse(process.env.KONFIG_JSON)
+catch error
+  console.log "buildclient: Error when trying to parse 'KONFIG_JSON'"
+  return

 args =
   watchDuration : argv.watchDuration  or 5000
@@ -43,6 +47,7 @@
     minus  = '-'
     bytes *= -1
   thresh    = 1024
+
   units     = ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
   unitIndex = -1
   return "#{bytes} B"  if bytes < thresh
@@ -580,7 +585,7 @@
   getProjects:->

     rp = (address)=>
-      "#{ address?.replace /^website\//, '/' }?#{ args.version }"
+      "#{ address?.replace(/^website\//, '/') }?#{ args.version }"

     apps = {}
     {projects, bundles} = require './projects'


Fixed response at backbone.offline

Fixed response at backbone.offline

--- vendor/assets/javascripts/backbone.offline.coffee
+++ vendor/assets/javascripts/backbone.offline.coffee
@@ -18,7 +18,10 @@
         when 'update' then store.update(model, options)
         when 'delete' then store.destroy(model, options)

-      if resp then options.success() else options.error?('Record not found')
+      if resp
+        options.success(resp.attributes ? resp)
+      else
+        options.error?('Record not found')

     # Overrides default 'Backbone.sync'. It checks 'storage' property of the model or collection
     # and then delegates to 'Offline.localSync' when property exists else calls the default 'Backbone.sync' with received params.


tests

tests

--- vendor/assets/javascripts/planning_view.js.coffee
+++ vendor/assets/javascripts/planning_view.js.coffee
@@ -116,11 +116,11 @@
 pa_planning_shortcuts = (e) ->
   unless window.pa_planning_ctrl_locker
     if e.keyCode == 37
-#move left
+      #move left
       window.pa_planning_ctrl_locker = true
       pa_planning_horizontal_move 10
     else if e.keyCode == 39
-#move right
+      #move right
       window.pa_planning_ctrl_locker = true
       pa_planning_horizontal_move -10

@@ -129,8 +129,35 @@
   window.pa_planning_ctrl_locker = false

 #  access properties of the wrapper's css rule
+window.CCSStylesheetRuleStyle2 = (stylesheet, selector, style, value = undefined) ->
+  property = undefined
+  for i in document.styleSheets
+    if i.title && i.title.indexOf stylesheet != -1
+      rules = i[if document.all then 'rules' else 'cssRules']
+      for j in rules
+        if j.selectorText == selector
+          property = j.style
+          break
+      break
+  if property == undefined
+    css = "#planning-view__container .planning-view__row-wrapper { #{property}: #{value} }"
+    head = document.head || document.getElementsByTagName('head')[0]
+    style = document.createElement 'style'
+    style.type = 'text/css'
+    style.title = 'pa-planning-cssRules'
+    if style.styleSheet
+      style.styleSheet.cssText = css;
+    else
+      style.appendChild(document.createTextNode css)
+  head.appendChild(style)
+  if value == undefined
+    return property[style]
+  else
+    return property[style] = value
+
+
 pa_planning_wrapper_property = (property, value = undefined) ->
-  return CCSStylesheetRuleStyle 'pa-planning-cssRules', '#planning-view__container .planning-view__row-wrapper', property, value
+  return CCSStylesheetRuleStyle2 'pa-planning-cssRules', '#planning-view__container .planning-view__row-wrapper', property, value

 #  move left or right the wrappers
 pa_planning_horizontal_move = (delta) ->


Follow mixin

Follow mixin

--- test/follow.coffee
+++ test/follow.coffee
@@ -27,6 +27,6 @@
   fastEnemy.include(Follow)
   fastEnemy.follow(player)

-  ok fastEnemy.I.velocity.equal(Point(0, ))
+  ok fastEnemy.I.velocity.equal(Point(0, -5))

 module()


Follow mixin

Follow mixin

--- test/follow.coffee
+++ test/follow.coffee
@@ -39,6 +39,6 @@
   rightEnemy.include(Follow)
   rightEnemy.follow(player)

-  ok rightEnemy.I.velocity.equal(Point(-1, 0)), 'rig'
+  ok rightEnemy.I.velocity.equal(Point(-1, 0)), 'rightEnemy '

 module()


tweak tests for new location...

tweak tests for new location...

--- test/lib/user.iced
+++ test/lib/user.iced
@@ -67,7 +67,7 @@
     esc = make_esc tmpcb, "User::check_if_exists"
     await fs.stat @homedir, esc defer()
     await fs.stat @keyring_dir(), esc defer()
-    await fs.stat path.join(@homedir, ".keybase", "config.json"), esc defer()
+    await fs.stat path.join(@homedir, ".config", "keybase", "config.json"), esc defer()
     cb true

   #-----------------


Matrix copy constructor

Matrix copy constructor

--- test/matrix.coffee
+++ test/matrix.coffee
@@ -19,8 +19,9 @@
    matrix = Matrix(1, 0, 0, 1, 10, 12)

    matrix2 = Matrix(matrix)
-
-   equals(matrix2, matrix1)
+
+
+   matrixEqual(matrix2, matrix)

   test "Matrix() (Identity)", ->
     matrix = Matrix()


Simplify some test code

Simplify some test code

--- test/server.coffee
+++ test/server.coffee
@@ -1,5 +1,3 @@
-http               = require('http')
-
 class this.Server
   constructor: () ->
     @spawn = require('child_process').fork
@@ -16,9 +14,7 @@
       @child = @spawn('server.js', ['./test/config.json'])
       @child.once 'message', (m) =>
         if m == 'ready'
-          http.get "http://localhost:9876/api/nodevent", ->
-            http.get "http://localhost:9876/api/protected", ->
-              fn()
+          fn()
       @child.once 'exit', =>
         @child = null;


ComputeController: use ComputeStateChecker to make sure we have latest state for machines

--- client/Main/providers/computecontroller.coffee
+++ client/Main/providers/computecontroller.coffee
@@ -18,6 +18,7 @@
         environment  : KD.config.environment

       @eventListener = new ComputeEventListener
+      @computeStateChecker = new ComputeStateChecker

       @on "MachineBuilt",     => do @reset
       @on "MachineDestroyed", => do @reset
@@ -93,6 +94,9 @@
             machines.push machine

         @machines = machines
+        @computeStateChecker.machines = machines
+        @computeStateChecker.start()
+
         cb null, machines  for cb in queue
         queue = []

@@ -304,6 +308,8 @@
       @eventListener.triggerState machine,
         status      : response.State
         percentage  : 100
+
+      response

     .timeout ComputeController.timeout


Do not add "undefined" outputs to compiled app.

--- client/app/MainApp/kodingappscontroller.coffee
+++ client/app/MainApp/kodingappscontroller.coffee
@@ -376,9 +376,10 @@

         _final = "(function() {\n\n/* KDAPP STARTS */"
         result.forEach (output)=>
-          _final += "\n\n/* BLOCK STARTS */\n\n"
-          _final += "#{output}"
-          _final += "\n\n/* BLOCK ENDS */\n\n"
+          if output
+            _final += "\n\n/* BLOCK STARTS */\n\n"
+            _final += "#{output}"
+            _final += "\n\n/* BLOCK ENDS */\n\n"
         _final += "/* KDAPP ENDS */\n\n}).call();"


default-apps: Created app routing rule.

default-apps: Created app routing rule.

--- client/landing/app/MainApp/kodingrouter.coffee
+++ client/landing/app/MainApp/kodingrouter.coffee
@@ -107,7 +107,7 @@
     if passOptions
       method += 'WithOptions'
       options = {model:models, route, query}
-
+
     KD.getSingleton("appManager").tell section, method, options ? models,
       (contentDisplay)=>
         routeWithoutParams = route.split('?')[0]
@@ -196,7 +196,11 @@
         else clear()

     createSectionHandler = (sec)=>
-      ({params:{name}, query})=> @openSection sec, name, query
+      ({params:{name, slug}, query})=>
+        if sec is "Develop"
+          @openSection slug, name, query
+        else
+          @openSection sec, name, query

     createContentHandler       = @bound 'createContentDisplayHandler'
     createStaticContentHandler = @bound 'createStaticContentDisplayHandler'
@@ -226,6 +230,7 @@
       '/:name?/Members'                 : createSectionHandler 'Members'
       '/:name?/Topics'                  : createSectionHandler 'Topics'
       '/:name?/Develop'                 : createSectionHandler 'StartTab'
+      '/:name?/Develop/:slug'           : createSectionHandler 'Develop'
       '/:name?/Apps'                    : createSectionHandler 'Apps'
       '/:name?/Account'                 : createSectionHandler 'Account'


Matching example module code with sample Framer project

--- vnc-example01.framer/modules/ViewNavigationController.coffee
+++ vnc-example01.framer/modules/ViewNavigationController.coffee
@@ -52,7 +52,7 @@
 		vncWidth  = @options.width
 		vncHeight = @options.height

-		view.states.add(
+		view.states.add
 			"#{ PUSH.UP }":
 				x: 0
 				y: -vncHeight
@@ -68,8 +68,6 @@
 			"#{ PUSH.DOWN }":
 				x: 0
 				y: vncHeight
-		)
-

 		view.states.animationOptions = @animationOptions

@@ -182,7 +180,7 @@
 # 	width:  Screen.width
 # 	height: Screen.height
 # 	backgroundColor: "red"
-# 	superLayer: vnc
+# 	parent: vnc

 ################################################################################
 # USAGE EXAMPLE 2 - Use default initialViewName "initialView" ##################
@@ -194,13 +192,13 @@
 # 	width:  Screen.width
 # 	height: Screen.height
 # 	backgroundColor: "red"
-# 	superLayer: vnc
+# 	parent: vnc

 # view2 = new Layer
 # 	width:  Screen.width
 # 	height: Screen.height
 # 	backgroundColor: "green"
-# 	superLayer: vnc
+# 	parent: vnc

 # view1.on Events.Click, -> vnc.transition view2
 # view2.on Events.Click, -> vnc.back()


Question marks are bad.

Question marks are bad.

--- vendor/assets/javascripts/brainstem/brainstem-sync.coffee
+++ vendor/assets/javascripts/brainstem/brainstem-sync.coffee
@@ -69,7 +69,7 @@

   if base?.data?.errorInterceptor?
     errorHandler = options.error
-    options.error = (jqXHR, textStatus, errorThrown) -> base?.data?.errorInterceptor?(errorHandler, model, options, jqXHR, params)
+    options.error = (jqXHR, textStatus, errorThrown) -> base.data.errorInterceptor(errorHandler, model, options, jqXHR, params)

   # Make the request, allowing the user to override any Ajax options.
   xhr = options.xhr = Backbone.ajax(_.extend(params, options))


Client:Activity: Added `moveScrollToBottom` method to scroll down to bottom when new message created.

--- client/activity/lib/components/publicchatpane/index.coffee
+++ client/activity/lib/components/publicchatpane/index.coffee
@@ -38,6 +38,15 @@

     ActivityFlux.actions.message.createMessage @channel('id'), body

+    @moveScrollToBottom()
+
+
+  moveScrollToBottom: ->
+
+    ChatPaneWrapper = @refs.ChatPaneWrapper.getDOMNode()
+    InfiniteScroll = ChatPaneWrapper.querySelector '.InfiniteScroll'
+    InfiniteScroll.scrollTop = InfiniteScroll.scrollHeight
+

   onScrollThresholdReached: ->

@@ -46,12 +55,15 @@

   render: ->
-    <ChatPane
-      thread={@props.thread}
-      className="PublicChatPane"
-      messages={@props.messages}
-      onSubmit={@bound 'onSubmit'}
-      onScrollThresholdReached={@bound 'onScrollThresholdReached'}
-    />
+    <div ref="ChatPaneWrapper">
+      <ChatPane
+        thread={@props.thread}
+        className="PublicChatPane"
+        messages={@props.messages}
+        onSubmit={@bound 'onSubmit'}
+        onScrollThresholdReached={@bound 'onScrollThresholdReached'}
+      />
+    </div>

+


video: participants emits event for talking states

--- client/app/lib/videocollaboration/types/participant.coffee
+++ client/app/lib/videocollaboration/types/participant.coffee
@@ -1,4 +1,6 @@
 kd = require 'kd'
+helper = require '../helper'
+
 ###*
  * Base class for representing a video participant.
  * OpenTok has 2 different idea of users. `Publisher` and `Subscriber`.
@@ -49,6 +51,10 @@
     super nick, videoData
     @type = 'subscriber'

+    helper.subscribeToAudioChanges @videoData,
+      started : => @emit 'TalkingDidStart'
+      stopped : => @emit 'TalkingDidStop'
+

 module.exports = participantTypes = { Publisher, Subscriber }


Fix vm hostnameAlias parser

Fix vm hostnameAlias parser

--- client/landing/app/MainApp/VirtualizationController.coffee
+++ client/landing/app/MainApp/VirtualizationController.coffee
@@ -544,17 +544,19 @@

     return {descriptions, hostTypes}

-  parseAlias : (alias)->
+  # This is a copy of JVM.parseAlias
+  # make sure to update this if change other one ~ GG
+  parseAlias:(alias)->
     # group-vm alias
-    if /^shared-[0-9]/.test alias
-      result = alias.match /(.*)\.([a-z0-9-]+)\.kd\.io$/
+    if /^shared\-[0-9]+/.test alias
+      result = alias.match /(.*)\.([a-z0-9\-]+)\.kd\.io$/
       if result
         [rest..., prefix, groupSlug] = result
         uid = parseInt(prefix.split(/-/)[1], 10)
         return {groupSlug, prefix, uid, type:'group', alias}
     # personal-vm alias
-    else if /^vm-[0-9]/.test alias
-      result = alias.match /(.*)\.([a-z0-9-]+)\.([a-z0-9-]+)\.kd\.io$/
+    else if /^vm\-[0-9]+/.test alias
+      result = alias.match /(.*)\.([a-z0-9\-]+)\.([a-z0-9\-]+)\.kd\.io$/
       if result
         [rest..., prefix, nickname, groupSlug] = result
         uid = parseInt(prefix.split(/-/)[1], 10)


cancel is no longer a button

cancel is no longer a button

--- test/course/request-student-id.spec.coffee
+++ test/course/request-student-id.spec.coffee
@@ -30,5 +30,5 @@
   it 'calls onCancel when cancel button is clicked', ->
     Testing.renderComponent( RequestStudentId, props: @props ).then ({dom}) =>
       expect(@props.onCancel).not.to.have.been.called
-      Testing.actions.click(dom.querySelector('.cancel .btn'))
+      Testing.actions.click(dom.querySelector('.cancel a'))
       expect(@props.onCancel).to.have.been.called


Delete endpoint for shots

Delete endpoint for shots

--- coffees/src/controllers/shot_controller.coffee
+++ coffees/src/controllers/shot_controller.coffee
@@ -46,4 +46,10 @@
         response.status(500).json(error: error)
     )

+  router.delete '/shots/:id', (request, response) ->
+    models.Shot.destroy({
+      where: {id: request.params.id},
+    }).then (shot) ->
+      response.status(204).json("")
+
   router


Add todo

Add todo

--- core/app/backbone/views/wheel/persistent_wheel_view.coffee
+++ core/app/backbone/views/wheel/persistent_wheel_view.coffee
@@ -7,6 +7,8 @@
       disbelieve: getHash opinionType.type, 'disbelieve'
       doubt: getHash opinionType.type, 'doubt'

+    # TODO: Remove global jQuery call. You can currently have only one
+    #       persistent wheel view per page
     $("input[name=opinion][value=#{opinionType.type}s]")
       .prop 'checked', true


test_lod2dheatmap: adjust chrGap to make equal-sized cells

--- test/lod2dheatmap/test_lod2dheatmap.coffee
+++ test/lod2dheatmap/test_lod2dheatmap.coffee
@@ -2,10 +2,23 @@

 # Example: chr 1 at bottom (default)
 d3.json "data.json", (data) ->
-    mychart = lod2dheatmap()
+    n = unique(data.chr).length
+    N = data.chr.length
+    chrGap = (700 - 2*n)/N + 2
+    mychart = lod2dheatmap({
+        altrectcolor:""
+        chrlinecolor:"black"
+        chrGap:chrGap})
     mychart(d3.select("div#chart1"), data)

 # Example: chr 1 at top
 d3.json "data.json", (data) ->
-    mychart = lod2dheatmap({oneAtTop:true})
+    n = unique(data.chr).length
+    N = data.chr.length
+    chrGap = (700 - 2*n)/N + 2
+    mychart = lod2dheatmap({
+        oneAtTop:true
+        altrectcolor:""
+        chrlinecolor:"black"
+        chrGap:chrGap})
     mychart(d3.select("div#chart2"), data)


fix ajax set up

fix ajax set up

--- BackofficeBundle/Resources/public/coffee/setUpCallAjax.coffee
+++ BackofficeBundle/Resources/public/coffee/setUpCallAjax.coffee
@@ -1,6 +1,6 @@
 $(document).ready ->
   $.ajaxSetup
-    beforeSend: ->
+    beforeSend: (event) ->
       if event != undefined && event.type != undefined
         if event.type == "submit"
           displayLoader($('.submit_form', event.currentTarget).parent())


remove debug c.l's

remove debug c.l's

--- components/backend/modules/files/server.coffee
+++ components/backend/modules/files/server.coffee
@@ -8,7 +8,6 @@
 fs = require "fs-extra"

 fs.move = (oldName, newName, cb)->
-  console.log("move", oldName, newName)
   fs.copy oldName, newName, (err)->
     if err? then return console.log err
     fs.unlink oldName, (err)->
@@ -24,7 +23,6 @@
     moduleSetting = setting

   app.on cfg.moduleName+':after:put', (req, res, file)->
-    console.log("afterPut file")
     title = file.getFieldValue "title"
     if req.params.crop
       gmImg = gm(dir+title)


Force update on drag end

Force update on drag end


Use plain html for markdown images to be able to set width and height

--- controls/Markdown/UploadImageButton.jsx.coffee
+++ controls/Markdown/UploadImageButton.jsx.coffee
@@ -23,7 +23,8 @@
         )

   markdownImage: (resp) ->
-    "![#{resp.url}](#{resp.url})"
+    # "![#{resp.url}](#{resp.url})"
+    "<img src=\"#{resp.url}\" alt=\"#{resp.alt || ""}\" width=\"#{resp.width}\" height=\"#{resp.height}\" />"

   openFilePrompt: ->
     @refs.input.getDOMNode().click()


Removed last part

Removed last part

--- core/app/backbone/views/learn_more_view.coffee
+++ core/app/backbone/views/learn_more_view.coffee
@@ -1,9 +1,5 @@
 class window.LearnMorePopupView extends Backbone.Marionette.ItemView
   className: 'learn-more learn-more-popup'
-  template: 'learn_more/content'
-
-class window.LearnMoreBottomView extends Backbone.Marionette.ItemView
-  className: 'learn-more learn-more-bottom'
   template: 'learn_more/content'

 class window.NDPLearnMoreBottomView extends Backbone.Marionette.ItemView


Workspace: Pane launcher is now vertical.

--- client/landing/app/CommonViews/workspace/views/workspacefloatingpanelauncher.coffee
+++ client/landing/app/CommonViews/workspace/views/workspacefloatingpanelauncher.coffee
@@ -2,8 +2,9 @@

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

-    options.cssClass = "workspace-launcher"
-    options.partial  = "+"
+    # options.cssClass = "workspace-launcher"
+    # options.partial  = "+"
+    options.cssClass  = "workspace-launcher vertical"

     super options, data


Improved object done counter.

Improved object done counter.

--- app/assets/javascripts/app/controllers/_integration/ldap.coffee
+++ app/assets/javascripts/app/controllers/_integration/ldap.coffee
@@ -100,7 +100,7 @@
     if _.isEmpty(job)
       @lastImport.html('')
       return
-    countDone = job.result.created + job.result.updated + job.result.unchanged + job.result.skipped
+    countDone = job.result.created + job.result.updated + job.result.unchanged + job.result.skipped + job.result.failed
     if !job.result.roles
       job.result.roles = {}
     for role_id, statistic of job.result.role_ids


Fix a problem that cannot apply ace theme in production

--- backend/app/assets/javascripts/comable/admin/application.coffee
+++ backend/app/assets/javascripts/comable/admin/application.coffee
@@ -12,6 +12,7 @@
 #= require ace/ace
 #= require ace/worker-html
 #= require ace/mode-liquid
+#= require ace/theme-monokai
 #= require moment
 #= require bootstrap-datetimepicker
 #= require comable/admin/dispatcher


Don't duplicate message container on every connection

--- src/runtimes/websocket.coffee
+++ src/runtimes/websocket.coffee
@@ -6,21 +6,23 @@
     @connection = null
     @protocol = 'noflo'
     @buffer = []
+    @container = null
     super definition

   getElement: ->
+    return @container if @container

     # DOM visualization for remote runtime output
-    container = document.createElement 'div'
+    @container = document.createElement 'div'
     messageConsole = document.createElement 'pre'
     previewImage = document.createElement 'img'
-    container.appendChild previewImage
-    container.appendChild messageConsole
+    @container.appendChild previewImage
+    @container.appendChild messageConsole

     @on 'network', (message) ->
       return unless message.command is 'output'

-      p = message.payload
+      p = message.payload
       if p.type? and p.type == 'previewurl'
         hasQuery = p.url.indexOf '?' != -1
         separator = if hasQuery then '&' else '?'
@@ -32,7 +34,7 @@
     @on 'disconnected', ->
       messageConsole.innerHTML = ''

-    container
+    @container

   connect: ->
     return if @connection or @connecting


Support whats|what is|what's

Support whats|what is|what's

--- src/scripts/working-on.coffee
+++ src/scripts/working-on.coffee
@@ -16,7 +16,7 @@

 module.exports = (robot) ->

-  robot.respond /what is @?([\w .\-]+) working on(\?)?$/i, (msg) ->
+  robot.respond /(what\'s|what is|whats) @?([\w .\-]+) working on(\?)?$/i, (msg) ->
     name = msg.match[1].trim()

     if name is "you"


Fix login with facebook

Fix login with facebook

--- src/server/Application.coffee
+++ src/server/Application.coffee
@@ -79,6 +79,8 @@
 				@users[user.id] = user;
 				session.login(user.id);
 			promise.ensure () => @onMeRequest(session, request, response)
+		else
+			@onMeRequest(session, request, response)

 	onLogoutRequest: (session, request, response) =>
 		session.logout();


define BAN_TIME_MS

define BAN_TIME_MS

--- src/smc-hub/auth-token.coffee
+++ src/smc-hub/auth-token.coffee
@@ -13,6 +13,8 @@

 # map {account_id:{user_account_id:timestamp}}
 ban = {}
+
+BAN_TIME_MS = 1000*60

 exports.get_user_auth_token = (opts) ->
     opts = defaults opts,  # temporary until types is more than just a WARNING


share: page size 100

share: page size 100

--- src/smc-hub/share/router.cjsx
+++ src/smc-hub/share/router.cjsx
@@ -3,7 +3,7 @@

 """

-PAGE_SIZE            = 50
+PAGE_SIZE            = 100

 os_path              = require('path')


db schema: fix description of projects / compute_image column

--- src/smc-util/db-schema.coffee
+++ src/smc-util/db-schema.coffee
@@ -793,7 +793,7 @@
             desc : 'If project is running, this is the quota that it is running with.'
         compute_image :
             type : 'string'
-            desc : 'Specify the name of the underlying (kucalc) compute image (default: "default")'
+            desc : "Specify the name of the underlying (kucalc) compute image (default: '#{DEFAULT_COMPUTE_IMAGE}')"
         addons :
             type : 'map'
             desc : 'Configure (kucalc specific) addons for projects. (e.g. academic software, license keys, ...)'


Add textarea to listen changes in neuron form

--- app/assets/javascripts/admin/neuron_form.js.coffee
+++ app/assets/javascripts/admin/neuron_form.js.coffee
@@ -4,7 +4,7 @@
 $(document).on "page:load", ->
   formHasChanged = false

-$(document).on "change", "#{selector} input", ->
+$(document).on "change", "#{selector} input, #{selector} textarea", ->
   formHasChanged = true

 $(document).on "page:before-change", ->


Fixed onsetDatetime for symptom

Fixed onsetDatetime for symptom

--- app/assets/javascripts/datatypes/symptom.js.coffee
+++ app/assets/javascripts/datatypes/symptom.js.coffee
@@ -17,7 +17,7 @@
   constructor: (@entry) ->
     super @entry
     @_abatementDatetime = @entry.abatementDatetime
-    @_onsetDatetime = @entry.onsetDatetime
+    @_onsetDatetime = @entry.start_time
     @_severity = @entry.severity

   ###


Replace load to init sticky

Replace load to init sticky

--- app/assets/javascripts/foundation_extras.js.coffee
+++ app/assets/javascripts/foundation_extras.js.coffee
@@ -2,7 +2,7 @@

   initialize: ->
     $(document).foundation()
-    $(window).trigger "load.zf.sticky"
+    $(window).trigger "init.zf.sticky"
     $(window).trigger "resize"

     clearSticky = ->


Fix bug where dollar values were not properly formatted

--- app/assets/javascripts/vimeo_integration.js.coffee
+++ app/assets/javascripts/vimeo_integration.js.coffee
@@ -115,7 +115,7 @@

       "
STEP 2: Confirm Order

-      #{action} will cost $#{task.cost}. Would you like to proceed?
+      #{action} will cost $#{task.cost.toFixed(2)}. Would you like to proceed?

Accept


change Gradebook cell input type to text

fixes CNVS-13247

- In Firefox when a user hit up on the first cell or down on the last cell,
  it would increment or decrement the respective number. This is the browser
  behavior for inputs with the number type, so the type was changed to text.

Test plan:
- In Firefox, open a gradebook, edit a top cell, and hit the up arrow.
  Confirm that the number retians its value.
- Then edit a bottom cell, and hit the down arrow. Confirm that number maintains
  its value.

Change-Id: I9149d5165c5a66cda63dc48f5c94497600c2fd18
Reviewed-on: https://gerrit.instructure.com/45449
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Cameron Matheson <1d572acbfa68c7c6e541c7b840d6b622e5c0dc91@instructure.com>
Reviewed-by: Spencer Olson <7cc0890d6f1f70810a023e7173eca5724d6c8e49@instructure.com>
QA-Review: Nathan Rogowski <2e8aa918660411855c6d44d5bb2da677aa033255@instructure.com>
QA-Review: Amber Taniuchi <ef4bfd1e6d278f6bc7d4a478f02be2cb9581a0a5@instructure.com>
Product-Review: Matt Fairbourn <5f353e1c97bb9cc046b9db20beca0b9392f6f1b2@instructure.com>

--- app/coffeescripts/gradebook2/SubmissionCell.coffee
+++ app/coffeescripts/gradebook2/SubmissionCell.coffee
@@ -135,7 +135,7 @@
       @$wrapper = $(@cellWrapper("""

-            <input type="number" #{@ariaLabel(submission.submission_type)} class="grade"/>/#{@opts.column.object.points_possible}
+            <input type="text" #{@ariaLabel(submission.submission_type)} class="grade"/>/#{@opts.column.object.points_possible}

       """, { classes: 'gradebook-cell-out-of-formatter' })).appendTo(@opts.container)


Fix issue in javascript

Fix issue in javascript

--- app/assets/javascripts/rails_powergrid/predicator/_base.coffee
+++ app/assets/javascripts/rails_powergrid/predicator/_base.coffee
@@ -24,7 +24,7 @@
   result.where = (condition, variables...) ->
     subQuery = RailsPowergrid.where(condition, variables).predicates

-    if subQuery instanceof (array)
+    if subQuery instanceof Array
       subQuery = subQuery[0]

     return RailsPowergrid.where(
@@ -36,7 +36,7 @@
   result.or_where = (condition, variables...) ->
     subQuery = RailsPowergrid.where(condition, variables).predicates

-    if subQuery instanceof (array)
+    if subQuery instanceof Array
       subQuery = subQuery[0]

     return RailsPowergrid.where(


imageSmoothingQuality resampling

imageSmoothingQuality resampling

--- app/assets/javascripts/uploadcare/utils/image-processor.coffee
+++ app/assets/javascripts/uploadcare/utils/image-processor.coffee
@@ -37,11 +37,13 @@
           df.notify(.2)
           isJPEG = @state() is 'resolved'

+          # start = new Date()
           op = ns.shrinkImage(e.target, settings)
           op.progress (progress) ->
             df.notify(.2 + progress * .6)
           op.fail(df.reject)
           op.done (canvas) ->
+            # console.log('shrink: ' + (new Date() - start))
             # start = new Date()
             format = 'image/jpeg'
             quality = settings.quality or 0.8
@@ -75,7 +77,6 @@
     if img.width * step * img.height * step < settings.size
       return df.reject('not required')

-    # start = new Date()
     sW = originalW = img.width
     sH = img.height
     ratio = sW / sH
@@ -85,9 +86,8 @@
     maxSquare = 5000000  # ios max canvas square
     maxSize = 4096  # ie max canvas dimensions

-    do run = ->
+    run = ->
       if sW <= w
-        # console.log('draw: ' + (new Date() - start))
         df.resolve(img)
         return

@@ -117,7 +117,25 @@
         df.notify((originalW - sW) / (originalW - w))
         run()

-    df.promise()
+    runNative = ->
+      canvas = document.createElement('canvas')
+      canvas.width = w
+      canvas.height = h
+      cx = canvas.getContext('2d')
+      cx.imageSmoothingQuality = 'high'
+      cx.drawImage(img, 0, 0, w, h)
+      img.src = '//:0'            # for image
+      img.width = img.height = 1  # for canvas
+      df.resolve(canvas)
+
+    cx = document.createElement('canvas').getContext('2d')
+    if 'imageSmoothingQuality' of cx
+      runNative()
+    else
+      run()
+
+    df.promise()
+

   ns.drawFileToCanvas = (file, mW, mH, bg, maxSource) ->
     # in -> file
@@ -176,6 +194,7 @@
         df.resolve(canvas, sSize)

     df.promise()
+

   #
   # Util functions


Update test

Update test

--- test/cycle-times-coffee.coffee
+++ test/cycle-times-coffee.coffee
@@ -24,7 +24,7 @@
   require("../src/cycle-times")(robot)

   it 'registers "cycle" listener', ->
-    expect(@robot.respond).to.have.been.calledWith /(septi)?cycle\s*([0-9])?/i
+    expect(@robot.respond).to.have.been.calledWith /(septi)?cycle\s*([0-9])?$/i

   it 'registers "checkpoint" listener', ->
     expect(@robot.respond).to.have.been.calledWith /c(heck)?p(oint)?\s*([0-9])?/i


[chore] cleaned up after a lint pass

[chore] cleaned up after a lint pass

--- test/test_chapter3-cont.coffee
+++ test/test_chapter3-cont.coffee
@@ -23,12 +23,18 @@
   it "Should handle basic returns blocks", ->
     expect(lisp read "(block foo (+ 1 (return-from foo 2)))").to.equal(2)
   it "Should handle complex returns blocks", ->
-    expect(lisp read "(block foo ((lambda (exit)(* 2 (block foo (* 3 (exit 5)) )) ) (lambda (x) (return-from foo x)) ) )").to.equal(5)
+    code = "(block foo ((lambda (exit)(* 2 (block foo (* 3 (exit 5)) )) ) (lambda (x) (return-from foo x)) ) )"
+    expect(lisp read code).to.equal(5)
   it "Expects an uninitialized return-from to fail", ->
     expect(-> lisp read "(return-from foo 3)").to.throw("Unknown block label foo")
   it "Expects to see an obsolete block when called late", ->
-    expect(-> lisp read "((block foo (lambda (x) (return-from foo x))) 3 )").to.throw("Obsolete continuation")
+    expect(-> lisp read "((block foo (lambda (x) (return-from foo x))) 3 )")
+      .to.throw("Obsolete continuation")
   it "Expects to see an obsolete block when called late", ->
-    expect(-> lisp read "((block a  (* 2 (block b (return-from a (lambda (x) (return-from b x))))) )3 )").to.throw("Obsolete continuation")
+    blocka = "((block a  (* 2 (block b (return-from a (lambda (x) (return-from b x))))) )3 )"
+    expect(-> lisp read blocka).to.throw("Obsolete continuation")
   it "Expects to see an obsolete block when called late", ->
-    expect(-> lisp read "((block a (* 2 (block b (return-from a (lambda (x) (return-from a x))))) ) 3 )").to.throw("Obsolete continuation")
+    blockb = "((block a (* 2 (block b (return-from a (lambda (x) (return-from a x))))) ) 3 )"
+    expect(-> lisp read blockb).to.throw("Obsolete continuation")
+
+


Dropped outdated methods for sending keepalives. Now keepalive is just a normal message.

--- src/trans-xhr.coffee
+++ src/trans-xhr.coffee
@@ -36,9 +36,6 @@
                 ondrain()
             return true

-    doKeepalive: () ->
-        @doSendFrame("h")
-

 class XhrStreamingReceiver extends transport.ResponseReceiver
     protocol: "xhr"
@@ -57,8 +54,6 @@
             @response.end()
         return r

-    doKeepalive: () ->
-        @doSendFrame("h")

 exports.app =


List only displays file name

List only displays file name

--- lib/file-list-view.coffee
+++ lib/file-list-view.coffee
@@ -12,10 +12,15 @@
   destroy: ->

   addFile: (filePath, initialLine) =>
-    #TODO only display the file name
     #TODO add click handler to change to new text editor... not sure how
     #TODO add styling
-    @list.append("<li>#{filePath}</li>")
+    fileName = filePath.replace(/^.*[\\\/]/, '')
+
+    listElement = document.createElement('li')
+    listElement.textContent = fileName
+    #listElement.classList.add("testClass")
+
+    @list.append(listElement)

   removeAllFiles: =>
     $(@list).empty()


Stack: Fetch firewall rules and list them in stack.

--- client/Environments/views/scene/environmentrulecontainer.coffee
+++ client/Environments/views/scene/environmentrulecontainer.coffee
@@ -2,15 +2,14 @@

   EnvironmentDataProvider.addProvider "rules", ->

-    dummyRules = [
-      {
-        title: "Allow All",
-        description: "allow from *"
-      }
-    ]
+    new Promise (resolve, reject) ->
+      KD.remote.api.JProxyFilter.fetch (err, filters) ->
+        if err or not filters or filters.length is 0
+          warn "Failed to fetch domains", err  if err
+          return resolve []

-    new Promise (resolve, reject)->
-      resolve dummyRules
+        filter.title = filter.name  for filter in filters
+        resolve filters

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


Quit IDE if machine was not approved before

--- client/app/lib/activity/sidebar/sidebarmachinesharepopup.coffee
+++ client/app/lib/activity/sidebar/sidebarmachinesharepopup.coffee
@@ -128,6 +128,7 @@
   approve: ->

     machine = @getData()
+    wasApproved = machine.isApproved()

     kd.singletons.machineShareManager.unset machine.uid

@@ -154,6 +155,9 @@
           @destroy()

       callback = =>
+
+        return @destroy()  if wasApproved
+
         envDataProvider.fetch =>
           # if there is an ide instance this means user landed to ide with direct url
           ideApp = envDataProvider.getIDEFromUId machine.uid


Update syntax rule.

Update syntax rule.

--- client/app/lib/payment/paymentdowngradewithdeletionmodal.coffee
+++ client/app/lib/payment/paymentdowngradewithdeletionmodal.coffee
@@ -1,10 +1,10 @@
-kd                  = require 'kd'
-KDButtonView        = kd.ButtonView
-KDCustomHTMLView    = kd.CustomHTMLView
-KDLoaderView        = kd.LoaderView
-PaymentBaseModal    = require './paymentbasemodal'
-showError           = require 'app/util/showError'
-PaymentConstants    = require './paymentconstants'
+kd               = require 'kd'
+KDButtonView     = kd.ButtonView
+KDCustomHTMLView = kd.CustomHTMLView
+KDLoaderView     = kd.LoaderView
+PaymentBaseModal = require './paymentbasemodal'
+showError        = require 'app/util/showError'
+PaymentConstants = require './paymentconstants'

 module.exports = class PaymentDowngradeWithDeletionModal extends PaymentBaseModal


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/string_extensions.coffee
+++ test/string_extensions.coffee
@@ -17,6 +17,8 @@

 test "#extension", ->
   equals "README".extension(), ""
+
+

 test "#humanize", ->
   equals "employee_salary".humanize(), "Employee salary"


Add overlay tests.

Add overlay tests.

--- test/unit/modelingSpec.coffee
+++ test/unit/modelingSpec.coffee
@@ -5,9 +5,6 @@
 define ['ngmocks', 'lodash', 'expect', 'moment', 'modeling', 'helpers'],
   (ng, _, expect, moment, modeling) ->
     describe 'Unit Testing the Modeling Service', ->
-      modelings = null
-      modeling = null
-
       # The mock objects.
       mock =
         protocol:
@@ -49,11 +46,16 @@
             }
           ]

+      subject = null
+      modelings = null
+      modeling = null
+
       beforeEach ->
         # Fake the modeling service module.
         ng.module('qiprofile.modeling')
         inject ['Modeling', (Modeling) ->
-            modelings = Modeling.collect(mock.subject)
+            subject = _.cloneDeep(mock.subject)
+            modelings = Modeling.collect(subject)
             try
               modeling = modelings[0]
             catch TypeError
@@ -88,6 +90,18 @@
                  " registration protocol")
             .to.equal(mock.protocol.registration._id)

+      describe 'Overlay', ->
+        it 'should have one modeling overlay', ->
+          expect(modeling.overlays, "Modeling is missing overlays")
+            .to.exist
+          expect(modeling.overlays.length, "Modeling overlays count" +
+                                           " is incorrect")
+            .to.equal(1)
+
+        it 'should recapitulate the modeling result delta Ktrans overlay', ->
+          expect(modeling.overlays[0], "Modeling result overlays is incorrect")
+            .to.equal(modeling.result.deltaKTrans.overlay)
+
       describe 'Result', ->
         result = null


refactor

refactor


Link to userpages from Assignment, and don't format titles as links unless there is a url

--- app/assets/javascripts/components/assignments/assignment.cjsx
+++ app/assets/javascripts/components/assignments/assignment.cjsx
@@ -1,5 +1,8 @@
 React             = require 'react/addons'
 ArticleStore      = require '../../stores/article_store'
+
+userLink = (wiki_id) ->
+  {wiki_id}

 Assignment = React.createClass(
   displayName: 'Assignment'
@@ -18,13 +21,26 @@
     ratingMobileClass = ratingClass + ' tablet-only'
     languagePrefix = if article.language then "#{article.language}:" else ''
     formattedTitle = "#{languagePrefix}#{article.title}"
+
+    if article.url
+      articleLink = {formattedTitle} {(if article.new_article then ' (new)' else '')}
+    else
+      articleLink = formattedTitle
+
+
     assignees = []
     reviewers = []
     for assignment in _.sortBy @props.assign_group, 'user_wiki_id'
       if assignment.role == 0
-        assignees.push assignment.user_wiki_id
+        assignees.push userLink(assignment.user_wiki_id)
+        assignees.push ', '
       else if assignment.role == 1
-        reviewers.push assignment.user_wiki_id
+        reviewers.push userLink(assignment.user_wiki_id)
+        reviewers.push ', '
+
+    assignees.pop() if assignees.length
+    reviewers.pop() if reviewers.length
+

@@ -37,11 +53,11 @@

{article.pretty_rating}

-          {formattedTitle} {(if article.new_article then ' (new)' else '')}
+          {articleLink}

-      {assignees.join(', ')}
-      {reviewers.join(', ')}
+      {assignees}
+      {reviewers}

 )


Added Gmaps.map.geolocationSuccess()

Added Gmaps.map.geolocationSuccess()

--- app/assets/javascripts/gmaps4rails/gmaps4rails.base.js.coffee
+++ app/assets/javascripts/gmaps4rails/gmaps4rails.base.js.coffee
@@ -24,6 +24,7 @@
     @userLocation = null       #contains user's location if geolocalization was performed and successful

     #empty slots
+    @geolocationSuccess = -> false  #triggered when geolocation succeeds. Can be customized.
     @geolocationFailure = -> false  #triggered when geolocation fails. If customized, must be like= function(navigator_handles_geolocation){} where 'navigator_handles_geolocation' is a boolean
     @callback           = -> false  #to let user set a custom callback function
     @customClusterer    = -> false  #to let user set custom clusterer pictures
@@ -95,6 +96,7 @@
         #change map's center to focus on user's geoloc if asked
         if(map_object.map_options.center_on_user == true)
           map_object.centerMapOnUser()
+        map_object.geolocationSuccess()
       positionFailure = ->
         map_object.geolocationFailure(true)


Propose caption with fade effect

Propose caption with fade effect

--- app/assets/javascripts/views/events/collection_view.js.coffee
+++ app/assets/javascripts/views/events/collection_view.js.coffee
@@ -15,10 +15,12 @@

   open: ->
     @$().find('.info').addClass('active')
+    @$().find('.nivo-caption').fadeOut(400)
     @$().find('.nivoSlider').data('nivoslider')?.stop()

   close: ->
     @$().find('.info').removeClass('active')
+    @$().find('.nivo-caption').fadeIn(400)
     @$().find('.nivoSlider').data('nivoslider')?.start()

 Yossarian.EventsCollectionView = Ember.CollectionView.extend


Re-add 0 padding issue in wallet/send/validation

--- app/src/controllers/wallet/send/wallet_send_validation_dialog_view_controller.coffee
+++ app/src/controllers/wallet/send/wallet_send_validation_dialog_view_controller.coffee
@@ -53,7 +53,14 @@
     decal = 0
     # add amount
     if validationDetails.needsAmountValidation
-      value = ledger.formatters.bitcoin.fromValue(validationDetails.amount.text, 3)
+      value = ledger.formatters.bitcoin.fromValue(validationDetails.amount.text)
+      # normalize value
+      dotIndex = value.indexOf '.'
+      if dotIndex == -1
+        value += '.000'
+      else
+        numDecimalDigits = value.length - 1 - dotIndex
+        value += _.str.repeat '0', 3 - numDecimalDigits
       string += value + ' BTC'
       indexes = indexes.concat validationDetails.amount.indexes[0]
       indexes = indexes.concat _.map(validationDetails.amount.indexes.slice(1), (num) => num + 1) # decalage virgule


Hide icons in new link blocks

Hide icons in new link blocks

--- topics/static/js/script.coffee
+++ topics/static/js/script.coffee
@@ -21,11 +21,6 @@

     <textarea class="commentary" placeholder="Commentary"></textarea>
-

-
-
-
-

     <input class="link_url" placeholder="Link title" value="">

         <input class="editing-link-url" name="url" placeholder="https://example.org" value="">


Insert some mocked changes

Insert some mocked changes

--- services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee
+++ services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee
@@ -23,6 +23,95 @@
 			content: ""

 		$scope.reviewPanelEventsBridge = new EventEmitter()
+
+		changesTrackers = {}
+
+		# TODO Just for prototyping purposes; remove afterwards.
+		mockedUserId = 'mock_user_id_1'
+		mockedUserId2 = 'mock_user_id_2'
+
+		if window.location.search.match /mocktc=true/
+			mock_changes = {
+				"main.tex":
+					changes: [{
+						op: { i: "Habitat loss and conflicts with humans are the greatest causes of concern.", p: 925 - 38 }
+						metadata: { user_id: mockedUserId, ts: new Date(Date.now() - 30 * 60 * 1000) }
+					}, {
+						op: { d: "The lion is now a vulnerable species. ", p: 778 }
+						metadata: { user_id: mockedUserId, ts: new Date(Date.now() - 31 * 60 * 1000) }
+					}]
+					comments: [{
+						offset: 1375 - 38
+						length: 79
+						metadata:
+							thread: [{
+								content: "Do we have a source for this?"
+								user_id: mockedUserId
+								ts: new Date(Date.now() - 45 * 60 * 1000)
+							}]
+					}]
+				"chapter_1.tex":
+					changes: [{
+						"op":{"p":740,"d":", to take down large animals"},
+						"metadata":{"user_id":mockedUserId, ts: new Date(Date.now() - 15 * 60 * 1000)}
+					}, {
+						"op":{"i":", to keep hold of the prey","p":920},
+						"metadata":{"user_id":mockedUserId, ts: new Date(Date.now() - 130 * 60 * 1000)}
+					}, {
+						"op":{"i":" being","p":1057},
+						"metadata":{"user_id":mockedUserId2, ts: new Date(Date.now() - 72 * 60 * 1000)}
+					}]
+					comments:[{
+						"offset":111,"length":5,
+						"metadata":{
+							"thread": [
+								{"content":"Have we used 'pride' too much here?","user_id":mockedUserId, ts: new Date(Date.now() - 12 * 60 * 1000)},
+								{"content":"No, I think this is OK","user_id":mockedUserId2, ts: new Date(Date.now() - 9 * 60 * 1000)}
+							]
+						}
+					},{
+						"offset":452,"length":21,
+						"metadata":{
+							"thread":[
+								{"content":"TODO: Don't use as many parentheses!","user_id":mockedUserId2, ts: new Date(Date.now() - 99 * 60 * 1000)}
+							]
+						}
+					}]
+				"chapter_2.tex":
+					changes: [{
+						"op":{"p":458,"d":"other"},
+						"metadata":{"user_id":mockedUserId, ts: new Date(Date.now() - 133 * 60 * 1000)}
+					},{
+						"op":{"i":"usually 2-3, ","p":928},
+						"metadata":{"user_id":mockedUserId, ts: new Date(Date.now() - 27 * 60 * 1000)}
+					},{
+						"op":{"i":"If the parents are a male lion and a female tiger, it is called a liger. A tigon comes from a male tiger and a female lion.","p":1126},
+						"metadata":{"user_id":mockedUserId, ts: new Date(Date.now() - 152 * 60 * 1000)}
+					}]
+					comments: [{
+						"offset":299,"length":10,
+						"metadata":{
+							"thread":[{
+								"content":"Should we use a different word here if 'den' needs clarifying?","user_id":mockedUserId,"ts": new Date(Date.now() - 430 * 60 * 1000)
+							}]
+						}
+					},{
+						"offset":843,"length":66,
+						"metadata":{
+							"thread":[{
+								"content":"This sentence is a little ambiguous","user_id":mockedUserId,"ts": new Date(Date.now() - 430 * 60 * 1000)
+							}]
+						}
+					}]
+			}
+			ide.$scope.$on "file-tree:initialized", () ->
+				ide.fileTreeManager.forEachEntity (entity) ->
+					if mock_changes[entity.name]?
+						changesTrackers[entity.id] ?= new ChangesTracker()
+						for change in mock_changes[entity.name].changes
+							changesTrackers[entity.id]._addOp change.op, change.metadata
+						for comment in mock_changes[entity.name].comments
+							changesTrackers[entity.id].addComment comment.offset, comment.length, comment.metadata

 		scrollbar = {}
 		$scope.reviewPanelEventsBridge.on "aceScrollbarVisibilityChanged", (isVisible, scrollbarWidth) ->
@@ -48,17 +137,11 @@
 			else
 				# Reset back to what we had when previously open
 				$scope.reviewPanel.subView = $scope.reviewPanel.openSubView
-
-
-		changesTrackers = {}

 		$scope.$watch "editor.open_doc_id", (open_doc_id) ->
 			return if !open_doc_id?
 			changesTrackers[open_doc_id] ?= new ChangesTracker()
 			$scope.reviewPanel.changesTracker = changesTrackers[open_doc_id]
-
-		# TODO Just for prototyping purposes; remove afterwards.
-		mockedUserId = '12345abc'

 		$scope.$watch (() ->
 			entries = $scope.reviewPanel.entries[$scope.editor.open_doc_id] or {}
@@ -127,7 +210,7 @@
 		# TODO Just for prototyping purposes; remove afterwards.
 		submitMockedReply = (entry) ->
 			entry.thread.push {
-				content: 'Lorem ipsum dolor sit amet'
+				content: 'Sounds good!'
 				ts: new Date()
 				user_id: mockedUserId
 			}
@@ -184,11 +267,18 @@
 			$scope.users = {}
 			# TODO Just for prototyping purposes; remove afterwards.
 			$scope.users[mockedUserId] = {
-				email: "gerald.butler@gmail.com"
-				name: "Gerald Butler"
+				email: "paulo@sharelatex.com"
+				name: "Paulo Reis"
 				isSelf: false
 				hue: 70
-				avatar_text: "G"
+				avatar_text: "PR"
+			}
+			$scope.users[mockedUserId2] = {
+				email: "james@sharelatex.com"
+				name: "James Allen"
+				isSelf: false
+				hue: 320
+				avatar_text: "JA"
 			}

 			for member in $scope.project.members.concat($scope.project.owner)


Removed a console.debug line

Removed a console.debug line

--- src/elements/Form/SimpleForm.coffee
+++ src/elements/Form/SimpleForm.coffee
@@ -483,8 +483,6 @@
 						ff.maximize_vertical == true
 							classes.push("cui-maximize-vertical")

-					console.debug("ff:", ff, classes)
-
 				row = CUI.dom.element("DIV", class: "cui-form-row "+classes.join(" "), "data-for-field": field.getUniqueId())
 				row.appendChild(get_append(field))
 				append(get_append(field), row)


:art:

:art:

--- src/browser/atom-application.coffee
+++ src/browser/atom-application.coffee
@@ -110,7 +110,6 @@
   addWindow: (window) ->
     @windows.push window
     @applicationMenu?.addWindow(window.browserWindow)
-
     window.once 'window:loaded', =>
       @autoUpdateManager.emitUpdateAvailableEvent(window)


Fix typo.

Fix typo.

--- src/coffee/orderdistribution.coffee
+++ src/coffee/orderdistribution.coffee
@@ -8,7 +8,7 @@
   constructor: (options = {}) ->
     super(options)
     throw new Error 'No master configuration in options!' unless options.master
-    throw new Error 'No master configuration in options!' unless options.retailer
+    throw new Error 'No retailer configuration in options!' unless options.retailer
     @masterRest = new Rest config: options.master
     @retailerRest = new Rest config: options.retailer
     @inventoryUpdater = new InventoryUpdater config: options.master


Keep tags sorting at the beginning

Keep tags sorting at the beginning

--- client/group.coffee
+++ client/group.coffee
@@ -60,16 +60,20 @@
 changeSortLink = (sortBy, key) ->
   reverse = key of columnReverse
   ## Remove this sort if it exists, to move it to the beginning.
+  seenNonTag = false
   newSortBy =
     for sort, i in sortBy
       if sort.key == key
         ## If this was already the first sort, reverse it.
-        reverse = not sort.reverse if i == 0
+        reverse = not sort.reverse unless seenNonTag
         continue
       else
+        seenNonTag = true unless sort.key.startsWith 'tag.'
         sort
   ## New sort at the beginning
   newSortBy.splice 0, 0, {key, reverse}
+  ## Put tags in front for clustering
+  newSortBy = _.sortBy newSortBy, (sort) -> not sort.key.startsWith 'tag.'
   linkToSort newSortBy

 Template.registerHelper 'groups', ->
@@ -206,6 +210,14 @@
         console.error "messageNew did not return problem -- not authorized?"

   <> {###
###}
+    {if tags.length
+      <TagEdit tags={tags} rootClassName="tagGather push-down"
+       className="label label-default"
+       href={(tag) -> changeSortLink sortBy, "tag.#{tag.key}"}>
+        {'Gather by tag '}
+
+      </TagEdit>
+    }
     {if superuser

         <button className="btn btn-warning sortSetDefault" onClick={onSortSetDefault}>
@@ -259,14 +271,6 @@

       </Dropdown.Menu>
     </Dropdown>
-    {if tags.length
-      <TagEdit tags={tags} rootClassName="tagGather push-down"
-       className="label label-default"
-       href={(tag) -> changeSortLink sortBy, "tag.#{tag.key}"}>
-        {'Gather by tag '}
-
-      </TagEdit>
-    }

       <TextTooltip title="Show all messages you have authored or been @mentioned in">
         <a className="btn btn-default myPostsButton #{unless user? then 'disabled'}"
@@ -314,6 +318,8 @@
   for sort in sortBy
     if sort.key.startsWith 'tag.'
       groupBy.push sort.key[4..]
+    else
+      break
   groupBy = null unless groupBy.length


#928 - improving question selecting and sorting

--- jsapp/xlform_model_view/view.surveyApp.coffee
+++ jsapp/xlform_model_view/view.surveyApp.coffee
@@ -122,6 +122,8 @@
       @__rowViews = new Backbone.Model()
       @ngScope = options.ngScope

+      $(document).on 'click', @deselect_rows
+
       @survey.on 'rows-add', @reset, @
       @survey.on 'rows-remove', @reset, @
       @survey.on "row-detail-change", (row, key, val, ctxt)=>
@@ -160,7 +162,17 @@
     forceSelectRow: (evt)->
       # forceSelectRow is used to mock the multiple-select key
       @selectRow($.extend({}, evt))
+    deselect_all_rows: () ->
+      @$('.survey__row').removeClass('survey__row--selected')
+
+    deselect_rows: (evt) =>
+      if @is_selecting
+        @is_selecting = false
+      else
+        @deselect_all_rows()
+
     selectRow: (evt)->
+      @is_selecting = true
       $et = $(evt.target)
       if $et.hasClass('js-blur-on-select-row')
         return
@@ -170,17 +182,28 @@
       # a way to ensure the event is not run twice when in nested .js-select-row elements
       _isIntendedTarget = $ect.closest('.survey__row').get(0) is $et.closest('.survey__row').get(0)
       if _isIntendedTarget
+        $target = $et.closest('.survey__row')
         if !evt.ctrlKey
-          selected_rows = @selectedRows()
-          target = $et.closest('.survey__row')
-          if !target.hasClass('survey__row--selected') || selected_rows.length > 1
-            $('.survey__row').removeClass('survey__row--selected')
-
-
-        $et.closest('.survey__row').toggleClass("survey__row--selected")
+          selected_rows = $target.siblings('.survey__row--selected')
+          if !$target.hasClass('survey__row--selected') || selected_rows.length > 1
+            @deselect_all_rows()
+
+
+
+        $target.toggleClass("survey__row--selected")
+        if $target.hasClass('survey__row--group')
+          $target.find('li').toggleClass("survey__row--selected", $target.hasClass("survey__row--selected"))
+
+        $group = $target.parents('.survey__row')
+        if $group.length > 0
+          @select_group_if_all_items_selected($group)

         @questionSelect()
         @$('.js-blur-on-select-row').blur()
+
+    select_group_if_all_items_selected: ($group) ->
+      $rows = $group.find('.survey__row')
+      $group.toggleClass('survey__row--selected', $rows.length == $rows.filter('.survey__row--selected').length)

     questionSelect: (evt)->
       @activateGroupButton(@selectedRows().length > 0)
@@ -313,6 +336,14 @@
       sortable_stop = (evt, ui)=>
         $(ui.item).trigger('survey__row-sortablestop')

+      sortable_start = (event, ui) =>
+        if ui.item.hasClass('survey__row--selected')
+          $selected_rows = @formEditorEl.find('survey__row--selected')
+          if $selected_rows.length > 1
+            $wrapper = $('div')
+            $selected_rows.remove().appendTo($wrapper)
+            ui.item = $wrapper
+
       @formEditorEl.sortable({
           # PM: commented out axis, because it's better if cards move horizontally and vertically
           # axis: "y"
@@ -324,6 +355,7 @@
           connectWith: ".group__rows"
           opacity: 0.9
           scroll: true
+          start: sortable_start
           stop: sortable_stop
           activate: sortable_activate_deactivate
           deactivate: sortable_activate_deactivate


Fixes #3982 - Draft Sharing: Improve sidebar icon order for ticket creation.

--- app/assets/javascripts/app/controllers/agent_ticket_create/sidebar_shared_draft.coffee
+++ app/assets/javascripts/app/controllers/agent_ticket_create/sidebar_shared_draft.coffee
@@ -26,4 +26,4 @@
       active_draft_id: @params.shared_draft_id
     )

-App.Config.set('400-SharedDraft', SidebarSharedDraft, 'TicketCreateSidebar')
+App.Config.set('110-SharedDraft', SidebarSharedDraft, 'TicketCreateSidebar')


Remove another verbose log object

Remove another verbose log object

--- app/coffee/DocManager.coffee
+++ app/coffee/DocManager.coffee
@@ -98,7 +98,7 @@
 						update.lines = lines
 					if updateRanges
 						update.ranges = ranges
-					logger.log { project_id, doc_id, oldDoc: doc }, "updating doc lines and ranges"
+					logger.log { project_id, doc_id }, "updating doc lines and ranges"

 					modified = true
 					rev += 1 # rev will be incremented in mongo by MongoManager.upsertIntoDocCollection


failsafe in formatExpirationDate for missing translation

--- app/js/services/utils.coffee
+++ app/js/services/utils.coffee
@@ -124,8 +124,8 @@
         s

     formatExpirationDate : (_date) ->
+        deferred = $q.defer()
         if not _date
-            deferred = $q.defer()
             deferred.resolve("")
             return deferred.promise

@@ -134,7 +134,7 @@
         diff = (date - Date.now()) / 1000.0
         #console.log _date,date,diff,date.toLocaleDateString() #20140930T191750 Tue Sep 30 2014 15:17:50 GMT-0400 (EDT) -71989.91 9/30/2014

-        return if diff <= 0
+        promise = if diff <= 0
             $translate("utils.expired")
         else if diff <= 60
             $translate("utils.seconds", {value: Math.round(diff)})
@@ -145,8 +145,17 @@
         else if diff <= 30 * 24 * 3600
             $translate("utils.days", {value: Math.round(diff/3600.0/24)})
         else
-            deferred = $q.defer()
+            null
+
+        if promise
+            promise.catch (error) ->
+                deferred.resolve(date.toLocaleDateString() + " #{error}")
+            promise.then (result) ->
+                deferred.resolve(result)
+        else
             deferred.resolve(date.toLocaleDateString())
-            deferred.promise
+
+        deferred.promise

+


some copyedits (#5717)

some copyedits (#5717)

Just wanted to fix a comma, then saw numerous other little things, some of which I fixed. Needs more work, and I shall be happy to help.


Rework getting selected workflow

Rework getting selected workflow

--- app/pages/project/index.cjsx
+++ app/pages/project/index.cjsx
@@ -77,7 +77,7 @@
       @updateSugarSubscription nextProps.project
       @context.geordi?.remember projectToken: nextProps.project?.slug
     else if nextProps.preferences?.preferences.selected_workflow isnt @state.selectedWorkflow?.id
-      @getSelectedWorkflow nextProps.project, nextProps.preferences
+      @getSelectedWorkflow(nextProps.project, nextProps.preferences)

   fetchInfo: (project) ->
     @setState
@@ -106,9 +106,16 @@
   getSelectedWorkflow: (project, preferences) ->
     @setState selectedWorkflow: 'PENDING'

-    preferredWorkflowID = preferences?.preferences.selected_workflow ? project.configuration?.default_workflow
+    # preference user selected workflow, then project owner set workflow, then default workflow
+    if preferences?.preferences.selected_workflow
+      preferredWorkflowID = preferences?.preferences.selected_workflow
+    else if preferences?.settings.workflow_id
+      preferredWorkflowID = preferences?.settings.workflow_id
+    else if project.configuration?.default_workflow
+      preferredWorkflowID = project.configuration?.default_workflow
+
     if preferredWorkflowID?
-      apiClient.type('workflows').get preferredWorkflowID
+      apiClient.type('workflows').get preferredWorkflowID, {}
         .then (workflow) =>
           if workflow.active
             @setState selectedWorkflow: workflow
@@ -253,6 +260,12 @@
   componentDidMount: ->
     @_boundForceUpdate = @forceUpdate.bind this
     @fetchProjectData @props.params.owner, @props.params.name, @props.user
+      # .then =>
+      #   # For testing! Simulating a new assignment coming in after some time.
+      #   setTimeout ( =>
+      #     console.log('set selected workfow')
+      #     @state.preferences.update "preferences.selected_workflow": '2333'
+      #   ), 5000

   componentWillReceiveProps: (nextProps) ->
     {owner, name} = nextProps.params


Fix for last este-library.

Fix for last este-library.

--- app/server/js/storage.coffee
+++ app/server/js/storage.coffee
@@ -1,13 +1,13 @@
 goog.provide 'server.Storage'

-goog.require 'este.labs.Storage'
+goog.require 'este.Storage'

-class server.Storage extends este.labs.Storage
+class server.Storage extends este.Storage

   ###*
     @param {app.Routes} routes
     @constructor
-    @extends {este.labs.Storage}
+    @extends {este.Storage}
     @final
   ###
   constructor: (@routes) ->


fix gene test

fix gene test

--- apps/gene/test/client.coffee
+++ apps/gene/test/client.coffee
@@ -22,7 +22,7 @@
     benv.teardown()

   beforeEach ->
-    { GeneView } = mod = benv.require resolve __dirname, '../client.coffee'
+    { GeneView } = mod = benv.requireWithJadeify resolve(__dirname, '../client.coffee'), ['RelatedArtistsTemplate']
     for klass in ['ArtistFillwidthList', 'FilterArtworksView', 'FollowButton', 'ShareView', 'RelatedGenesView', 'BlurbView']
       @[klass] = sinon.stub()
       @[klass]::fetchAndRender = sinon.stub()
@@ -96,4 +96,4 @@

     it 'sets the state to artwork mode', ->
       @view.artworksMode()
-      @view.$('#gene-filter').data('state').should.equal 'artworks'+      @view.$('#gene-filter').data('state').should.equal 'artworks'


update archive name

update archive name

--- core/startup.coffee
+++ core/startup.coffee
@@ -3,7 +3,8 @@
 Path     = require 'path'
 MewBot   = require './mewbot.coffee'
 Fse      = require 'fs.extra'
-Cps      = require "child_process"
+Cps      = require 'child_process'
+Moment   = require 'moment'

 checkDirectory = (path)->
     dataFile = Path.join __dirname,"..",path
@@ -92,7 +93,7 @@
     if value
         Options.archive = value
     else
-        Options.archive = "mewbot-#{new Date().getTime()}"
+        Options.archive = "mewbot-#{new Moment().format()}"

 Parser.on "name",(opt,value)->
     if value and value.length


FIX: Add username to signin redirection

FIX: Add username to signin redirection

--- src/app/sessions/states/sign-in/sign-in.coffee
+++ src/app/sessions/states/sign-in/sign-in.coffee
@@ -41,7 +41,7 @@
     if $scope.aafLogin
       if $stateParams.authToken
         # This is AAF and we just got an auth_token? Must request to sign in
-        $scope.signIn({ auth_token: $stateParams.authToken })
+        $scope.signIn({ auth_token: $stateParams.authToken, username: $stateParams.username })
       else
         # We are AAF and no auth token so we can must redirect to AAF login provider
         window.location.assign($scope.aafLogin)


Show arrow head on connectors while dragging [#119384467]

--- src/code/utils/js-plumb-diagram-toolkit.coffee
+++ src/code/utils/js-plumb-diagram-toolkit.coffee
@@ -61,7 +61,9 @@
       dropOptions:
         activeClass: "dragActive"
       anchor: "Center"
+      connectorStyle : { strokeStyle:"#666" }
       endpoint: @_endpointOptions("Rectangle", 19, 'node-link-button')
+      connectorOverlays: [["Arrow", {location:1.0, width:10, length:10}]]
       maxConnections: -1
     )

@@ -82,7 +84,7 @@
       isTarget: true
       isSource: false
       anchor: "Center"
-      endpoint: @_endpointOptions("Rectangle", size, 'node-link-target')
+      endpoint: @_endpointOptions("Rectangle", size, 'node-link-target' )
       maxConnections: -1
       dropOptions:
         activeClass: "dragActive"


add logger into project flusher

add logger into project flusher

--- services/document-updater/app/coffee/ProjectFlusher.coffee
+++ services/document-updater/app/coffee/ProjectFlusher.coffee
@@ -6,6 +6,7 @@
 async = require("async")
 ProjectManager = require("./ProjectManager")
 _ = require("lodash")
+logger = require("logger-sharelatex")

 ProjectFlusher =


fix a broken test

fix a broken test

--- source/javascripts/qunit/index/weekTableFactoryTest.coffee
+++ source/javascripts/qunit/index/weekTableFactoryTest.coffee
@@ -174,7 +174,7 @@
     7:06:40
     6:55
     7:25
-    תַּעֲנִית
+    צוֹם גְּדַלְיָה

     Thursday


Proper fix for recursive polymorphic algebraic data

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -323,6 +323,8 @@
       {name, kind} = dataType.op
     else
       {name, kind} = dataType
+    if (lookupInMap @_scope().typeNames, name) instanceof TempKind
+      removeFromMap @_scope().typeNames, name
     addToMap @_scope().typeNames, name, kind

   kindOfTypeName: (name) ->
@@ -870,6 +872,8 @@
           ctx.kindOfTypeName atom.symbol
         else
           expectedKind or star
+      if kindOfType instanceof TempKind
+        kindOfType = expectedKind
       if not kindOfType
         # throw new Error "type name #{atom.symbol} was not defined" unless kind
         malformed atom, "This type name has not been defined"
@@ -878,7 +882,7 @@
     else
       expanded
   finalKind = kind type
-  if finalKind instanceof KindFn
+  if expectedKind instanceof KindFn
     labelOperator atom
   else
     atom.label = 'typename'
@@ -1278,6 +1282,9 @@
   varNames = map _symbol, typeParams
   varNameSet = arrayToSet varNames
   kinds = newMap()
+
+  # Fake kind for recursive use
+  ctx.addTypeName new TypeConstr dataName, new TempKind

   # TODO: I need to figure out the error handling, we should bail out
   #       if an undeclared type var is used
@@ -3633,6 +3640,8 @@

 class KindFn
   constructor: (@from, @to) ->
+class TempKind
+  constructor: ->

 class TypeVariable
   constructor: (@name, @kind) ->


Fix freshening in completion by type

Fix freshening in completion by type

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -4530,6 +4530,17 @@
   else
     newMap()

+freshenType = (type) ->
+  if type.TypeVariable
+    if type.ref.val
+      freshenType type.ref.val
+    else
+      new TypeVariable type.name, type.kind
+  else if type.TypeApp
+    new TypeApp (freshenType type.op), (freshenType type.arg)
+  else
+    type
+
 # findRefs = (type) ->
 #   if type.TypeVariable
 #     newMapWith type.name, type
@@ -5545,7 +5556,7 @@
     #   not isFailed mostGeneralUnifier (freshInstance ctx, def.type).type, type.type
     # [typed, notTyped] = partitionMap typesUnify, validDefinitions
     scoreAndPrint = (def) ->
-      freshedType = substitute newMap(), type.type
+      freshedType = freshenType type.type
       checkedType = (freshInstance ctx, def.type).type
       sub = mostGeneralUnifier checkedType, freshedType
       if isFailed sub


Better type errroing, fixed tokenization of chars with delims, typing delims and optimizing updating display

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -9,8 +9,8 @@
       | [#{controls}] # delims
       | /([^\s]|\\/)([^/]|\\/)*?/ # regex
       | "(?:[^"\\]|\\.)*" # strings
-      | \\[^\x20#{controls}]+ # char
-      | [^#{controls}"'\s]+ # normal tokens
+      | \\[^\s][^\s#{controls}]* # char
+      | [^#{controls}"'\\\s]+ # normal tokens
       )///
     if not match
       throw new Error "Could not recognize a token starting with `#{input[0..10]}`"
@@ -70,23 +70,24 @@

 constantLabeling = (atom) ->
   {symbol} = atom
-  labelMapping atom,
-    ['numerical', /^~?\d+/.test symbol]
-    ['label', isLabel atom]
-    ['string', /^"/.test symbol]
-    ['char', /^\\/.test symbol]
-    ['regex', /^\/[^\s\/]/.test symbol]
-    ['const', /^[A-Z][^\s\.]*$/.test symbol] # TODO: instead label based on context
-    ['paren', symbol in ['(', ')']]
-    ['bracket', symbol in ['[', ']']]
-    ['brace', symbol in ['{', '}']]
-    ['whitespace', /^\s+$/.test symbol]
+  labelMapping atom, [
+    ['numerical', -> /^~?\d+/.test symbol]
+    ['label', -> isLabel atom]
+    ['string', -> /^"/.test symbol]
+    ['char', -> /^\\/.test symbol]
+    ['regex', -> /^\/[^\s\/]/.test symbol]
+    ['const', -> /^[A-Z][^\s\.]*$/.test symbol] # TODO: instead label based on context
+    ['paren', -> symbol in ['(', ')']]
+    ['bracket', -> symbol in ['[', ']']]
+    ['brace', -> symbol in ['{', '}']]
+    ['whitespace', -> /^\s+$/.test symbol]
+  ]

 noWhitespace = (tokens) ->
   tokens.filter (token) -> token.label not in ['whitespace', 'indent']

-labelMapping = (word, rules...) ->
-  for [label, cond] in rules when cond
+labelMapping = (word, rules) ->
+  for [label, cond] in rules when cond()
     word.label = label
     return word
   word
@@ -729,7 +730,8 @@
   #   map [0: "hello" 1:] {"world", "le mond", "svete"}
   # TODO: should we support bare records?
   #   [a: 2 b: 3]
-  form.tea = tupleOfTypes (tea for {tea} in elems)
+  if not ctx.shouldDefer()
+    form.tea = tupleOfTypes (tea for {tea} in elems)

   if ctx.assignTo()
     combinePatterns compiledElems
@@ -2263,7 +2265,7 @@

 isCall = (expression) ->
   (isForm expression) and (isEmptyForm expression) and
-    expression[0].label is 'paren'
+    expression[0].symbol is '('

 isRecord = (expression) ->
   if isTuple expression
@@ -2271,10 +2273,10 @@
     labels.length is values.length and (allMap isLabel, labels)

 isSeq = (expression) ->
-  (isForm expression) and expression[0].label is 'brace'
+  (isForm expression) and expression[0].symbol is '{'

 isTuple = (expression) ->
-  (isForm expression) and expression[0].label is 'bracket'
+  (isForm expression) and expression[0].symbol is '['

 isEmptyForm = (form) ->
   (_terms form).length > 0
@@ -2283,7 +2285,7 @@
   Array.isArray expression

 isLabel = (atom) ->
-  /:$/.test atom.symbol
+  /[^\\]:$/.test atom.symbol

 isCapital = (atom) ->
   /[A-Z]/.test atom.symbol
@@ -3896,12 +3898,14 @@
     toInject = collectRequiresFor moduleName
     ctx = injectedContext toInject
     {js, ast} = compileCtxAstToJs topLevel, ctx, (astFromSource "(#{source})", -1, -1)
-    checkTypes ctx
     replaceOrAddToMap compiledModules, moduleName,
       declared: (subtractContexts ctx, (injectedContext toInject)) # must recompute because ctx is mutated
       js: js
     (attachPrintedTypes ctx, ast)
-    {js, ast: ast, types: typeEnumaration ctx}
+    js: js
+    ast: ast
+    types: typeEnumaration ctx
+    errors: checkTypes ctx

 compileExpression = (source, moduleName = '@unnamed') ->
   module = lookupInMap compiledModules, moduleName
@@ -3910,16 +3914,16 @@
   ast = (astFromSource "(#{source})", -1, -1)
   [expression] = _terms ast
   {js} = compileCtxAstToJs topLevelExpression, ctx, expression
-  checkTypes ctx
   (attachPrintedTypes ctx, expression)
   js: library + immutable + (listOfLines map lookupJs, setToArray toInject) + js
   ast: ast
+  errors: checkTypes ctx

 # Primitive type checking for now
 checkTypes = (ctx) ->
   # failed = mapToArray filterMap ((name) -> name is 'could not unify'), ctx.substitution
   if _notEmpty (failed = ctx.substitution.fails)
-    throw new Error failed[0]
+    failed

 lookupJs = (moduleName) ->
   js = (lookupInMap compiledModules, moduleName)?.js
@@ -4558,6 +4562,14 @@
       [[z y] g] x)
   """
   "(f [[2 42] 3])", 42
+
+  'deferring in tuples'
+  """
+    g [f {} 3]
+    f 4
+    [o t r] g
+  """
+  "o", 4
   # The following doesn't work because the Collection type class specifies
   # that the constructor takes only one argument.
   #


[neo4j] added all types of activities into async.parallel

--- server/lib/server/index.coffee
+++ server/lib/server/index.coffee
@@ -115,7 +115,7 @@
       res.send decorated

 app.get "/-/cache/latest", (req, res)->
-  async.parallel [fetchFollows, fetchMembers], (err, results)=>
+  async.parallel [fetchSingles, fetchFollows, fetchInstalls, fetchMembers], (err, results)=>
     res.send decorateAll(err, results)

 app.get "/-/cache/before/:timestamp", (req, res)->


Get the right message object

Get the right message object

--- scripts/arrivals.coffee
+++ scripts/arrivals.coffee
@@ -59,8 +59,8 @@
     (msg) ->
       msg.room is 'C5F138J1K' and msg.message?.rawMessage?.upload
     (res) ->
-      console.log "Respond! %j", res.message
-      file = res.message.rawMessage.files[0]
+      message = res.message
+      file = message.rawMessage.files[0]
       greyhound = util.slugify file.title
       name = util.capitalize file.title
       picUrl = file.thumb_1024 ? file.url_private
@@ -70,8 +70,8 @@
           name: res.message.user?.real_name
           email: res.message.user?.email_address

-      if res.message.text?
-        info = site.newInfo greyhound, res.message.text
+      if message.text
+        info = site.newInfo greyhound, message.text
         gitOpts.message = "Add #{name}! 🌟"
         res.reply "Adding #{name} to Available Hounds! 🌟\n" +
                   "Hang on a sec..."


sidebar: set inv machine id to null when clicked outside

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/app/lib/components/sidebarmachineslistitem/index.coffee
+++ client/app/lib/components/sidebarmachineslistitem/index.coffee
@@ -153,7 +153,10 @@
     return null  unless @machine 'isApproved'
     return null  unless @state.activeLeavingMachine

+    { setActiveLeavingSharedMachineId } = actions
+
     <LeaveSharedMachineWidget
+      onClose={-> setActiveLeavingSharedMachineId null}
       coordinates={@state.coordinates}
       machine={@props.machine} />


Config file name now strict specified.

Config file name now strict specified.

--- src/renderer/scripts/app/App.coffee
+++ src/renderer/scripts/app/App.coffee
@@ -36,6 +36,7 @@
         @menu = new MenuManager
         @config = new ConfigManager
             configDirPath : Electron.getDataPath()
+            configFileName : "config.json"
         @config.load()

         @nsenStream = new NsenStream


further class node tests

further class node tests

--- test/nodes/classes.coffee
+++ test/nodes/classes.coffee
@@ -19,3 +19,15 @@
   'root node should be class type'
 assertEqual 'Foo', parsed.name,
   'class name should be parsed'
+assertEqual 1, parsed.modifiers.length,
+  'the modifiers should be parsed'
+assertEqual 'public', parsed.modifiers[0],
+  'public visibility should parse'
+assertEqual 1, parsed.implements.length,
+  'interfaces implemented should parse'
+assertEqual 'Bar', parsed.implements[0],
+  'interface names should parse'
+assertEqual 1, parsed.extends.length,
+  'classed extended should parse'
+assertEqual 'Baz', parsed.extends[0],
+  'abstract class names should parse'


jenkins-notifier.coffee: Encode project urls with whitespaces

Signed-off-by: Philipp Beckmann <6ebf6b6479c01d1560656435e24a71ef1da2ad7a@pabnet.de>

--- src/scripts/jenkins-notifier.coffee
+++ src/scripts/jenkins-notifier.coffee
@@ -43,7 +43,7 @@
             build = "STILL"
           else
             build = "STARTED"
-          robot.send user, "#{build} FAILING: #{data.name} ##{data.build.number} (#{data.build.full_url})"
+          robot.send user, "#{build} FAILING: #{data.name} ##{data.build.number} (#{encodeURI(data.build.full_url)})"
           @failing.push data.name unless data.name in @failing
         if data.build.status == 'SUCCESS'
           if data.name in @failing


fixed off by 1 in browser history

fixed off by 1 in browser history

--- src/scripts/helpers/backbone/views/base.coffee
+++ src/scripts/helpers/backbone/views/base.coffee
@@ -68,7 +68,17 @@

     # Update page title, canonical link and Open Graph tags
     updatePageInfo: () ->
-      document.title = @pageTitle + settings.titleSuffix if @pageTitle
+      if @pageTitle
+        # If this is a page in a book/collection
+        if @model? and linksHelper.getCurrentPathComponents().page?
+          # Only update title if the new one belongs to the current page
+          if @model.getPageNumber().toString() is linksHelper.getCurrentPathComponents().page
+            document.title = @pageTitle + settings.titleSuffix
+          if @model.getPageNumber().toString() is '1' and linksHelper.getCurrentPathComponents().page is ''
+            document.title = @pageTitle + settings.titleSuffix
+        # Just update the title if it's a standalone page
+        else
+          document.title = @pageTitle + settings.titleSuffix

       canonical = @canonical?() or @canonical
       if canonical isnt undefined


test for observer

test for observer

--- test/test_observer.coffee
+++ test/test_observer.coffee
@@ -1,6 +1,52 @@

-Test('observer').run ($test, alight) ->
+Test('observer #0').run ($test, alight) ->
+    if not alight.debug.useObserver
+        $test.close()
+        return
+    $test.start 8
+
+    scope0 =
+        user:
+            name: 'macos'
+    scope1 =
+        user:
+            name: 'windows'
+
+    observer = alight.observer.create()
+    a0 = 0
+    ob0 = observer.observe scope0
+    ob0.watch 'user.name', ->
+        a0++
+    a1 = 0
+    ob1 = observer.observe scope1
+    ob1.watch 'user.name', ->
+        a1++
+
+    observer.deliver()
+    $test.equal a0, 0
+    $test.equal a1, 0
+
+    scope0.user.name = 'linux'
+    observer.deliver()
+    $test.equal a0, 1
+    $test.equal a1, 0
+
+    scope1.user.name = 'freebsd'
+    observer.deliver()
+    $test.equal a0, 1
+    $test.equal a1, 1
+
+    scope0.user.name = 'a'
+    scope1.user.name = 'b'
+    observer.deliver()
+    $test.equal a0, 2
+    $test.equal a1, 2
+
+    $test.close()
+
+
+Test('observer #1').run ($test, alight) ->
     if not alight.debug.useObserver
         $test.close()
         return


webapp: fix grepping through files and subdirs starting with a dash -- issue #995

--- src/smc-webapp/project_store.coffee
+++ src/smc-webapp/project_store.coffee
@@ -1006,14 +1006,14 @@

         if store.get('subdirectories')
             if store.get('hidden_files')
-                cmd = "rgrep -I -H --exclude-dir=.smc --exclude-dir=.snapshots #{ins} #{search_query} *"
+                cmd = "rgrep -I -H --exclude-dir=.smc --exclude-dir=.snapshots #{ins} #{search_query} -- *"
             else
-                cmd = "rgrep -I -H --exclude-dir='.*' --exclude='.*' #{ins} #{search_query} *"
+                cmd = "rgrep -I -H --exclude-dir='.*' --exclude='.*' #{ins} #{search_query} -- *"
         else
             if store.get('hidden_files')
-                cmd = "grep -I -H #{ins} #{search_query} .* *"
+                cmd = "grep -I -H #{ins} #{search_query} -- .* *"
             else
-                cmd = "grep -I -H #{ins} #{search_query} *"
+                cmd = "grep -I -H #{ins} #{search_query} -- *"

         cmd += " | grep -v #{MARKERS.cell}"
         max_results = 1000


refactor(util/addInstalledPackage): put callback into the event loop queue

Don't execute callback immediately in an async method.

--- src/util/addInstalledPackage.coffee
+++ src/util/addInstalledPackage.coffee
@@ -15,6 +15,7 @@
 	setCurrentProfile currentProfile
 	winston.verbose "util.addInstalledPackage: success, " +
 		"returning nothing"
-	callback undefined, yes
+	setTimeout ->
+		callback undefined, yes

 module.exports = addInstalledPackage


redirect for single course regardless of role

--- tutor/src/components/course-listing/index.cjsx
+++ tutor/src/components/course-listing/index.cjsx
@@ -20,7 +20,7 @@
     router: React.PropTypes.object

   shouldRedirect: (past, current) ->
-    current.length is 1 and CurrentUserStore.getCourseRole(current[0].id) is 'student'
+    current.length is 1

   shouldShowEmpty: (past, current) ->
     _.isEmpty(past) and _.isEmpty(current) # and some way to determine if student?!


Made item slot check to use config, and default to 10

--- views/components/main/parts/teitoku-panel.cjsx
+++ views/components/main/parts/teitoku-panel.cjsx
@@ -116,7 +116,8 @@
               error __ "Attention! Ship Slot has only %s left.", "#{@state.maxChara - @state.shipCount}"
             , 1000
         if config.get 'poi.mapstartcheck.item'
-          if @state.maxSlotitem - @state.slotitemCount <= 0
+          freeItemSlot = config.get 'poi.mapstartcheck.freeItemSlot', 10
+          if @state.maxSlotitem - @state.slotitemCount < freeItemSlot
             # toggleModal '装备检查', "装备已满，出击注意！"
             setTimeout =>
               error __ "Attention! Item Slot is full."


Grid-list now fully working

Grid-list now fully working

--- frontend/coffeescript/app/webform/views.coffee
+++ frontend/coffeescript/app/webform/views.coffee
@@ -149,7 +149,6 @@
           side2 = equation.slice(equation.lastIndexOf(" ") + 1)
           side1 = $("#" + side1.slice(2, -1)).val()  if side1.slice(0, 2) is "${"
           side2 = $("#" + side2.slice(2, -1)).val()  if side2.slice(0, 2) is "${"
-          console.log side1 + ", " + operation + ", " + side2
           if operation is "-"
             return (side1 - side2)
           else if operation is "+"
@@ -167,7 +166,7 @@
           if @input_fields[question].control

             # Field-list controls
-            if @input_fields[question].control.appearance is "field-list" or (@input_fields[question].control.appearance is "grid-list" and $('#' + @input_fields[question].name + '_field').hasClass('grid-list'))
+            if @input_fields[question].control.appearance is "field-list"
               current_tree = @input_fields[question].tree
               $('#' + @input_fields[question].name + '_field')
                 .fadeIn(1)
@@ -176,16 +175,16 @@
               question_info = @input_fields[question]

               while question_info.tree is current_tree
-                switch_question = $('#' + $($('.control-group').eq(question)[0]).data('key') + "_field")
-                switch_question.fadeIn(1).addClass('active')
+                question_change = $('#' + $($('.control-group').eq(question)[0]).data('key') + "_field")
+                question_change.fadeIn(1).addClass('active')
                 $('.active input').focus()
                 question++
                 question_info = @input_fields[question]

-            # Grid-list controls
-            else if @input_fields[question].control.appearance is "grid-list"
+            # Grid-list controls (Not already a grid list)
+            else if @input_fields[question].control.appearance is "grid-list" and !($('#' + @input_fields[question].name + '_field').hasClass('grid-list'))
               current_tree = @input_fields[question].tree
-              console.log(@input_fields[question])
+
               # Create the table/grid (as divs)!
               table_name = @input_fields[question].name + '_table'
               $('#' + @input_fields[question].name + '_field')
@@ -207,44 +206,38 @@

               while question_info.tree is current_tree
                 grid_row+=1
-                switch_question = $('#' + question_info.name + "_field")
+                question_change = $('#' + question_info.name + "_field")

                 # First, change the select to a list, then change the options to radio buttons
                 attrs = { }
-                #$.each($('#' + switch_question[0].id + ' select')[0].attributes, (idx, attr) ->
-                #  attrs[attr.nodeName] = attr.value
-                #)
-                #$('#' + switch_question[0].id + ' select').replaceWith( () ->
-                #  return $("
", attrs).append($(@).contents()) )
-
-                $('#' + switch_question[0].id + ' option').each( (index) ->
+
+                $('#' + question_change[0].id + ' option').each( (index) ->
                   attrs = { }
                   $.each($(@)[0].attributes, (idx, attr) ->
                     attrs[attr.nodeName] = attr.value
                   )
                   attrs.type = 'radio'
-                  attrs.name = switch_question.data('key')
-                  attrs.id = switch_question.data('key') + '-' + index
+                  attrs.name = question_change.data('key')
+                  attrs.id = question_change.data('key') + '-' + index
                   $(@).replaceWith( () ->
                     return $("<input />", attrs) )
                   $('#' + attrs.id)
-                    .appendTo($(switch_question))
+                    .appendTo($(question_change))
                     .wrap('')
                 )

-                $('#' + switch_question[0].id + ' label').wrap('')
-
-                $('#' + switch_question[0].id + ' .controls').remove()#.attr('display', 'inline-block')
+                $('#' + question_change[0].id + ' label').wrap('')
+                $('#' + question_change[0].id + ' .controls').remove()

                 # Then, move the question into the grid-list as a grid-row
-                switch_question.appendTo($('#' + table_name + ' tbody'))
+                question_change.appendTo($('#' + table_name + ' tbody'))
                 attrs = { }
-                $.each(switch_question[0].attributes, (idx, attr) ->
+                $.each(question_change[0].attributes, (idx, attr) ->
                   attrs[attr.nodeName] = attr.value
                 )
                 attrs.class = "active"

-                switch_question
+                question_change
                   .fadeIn(1)
                   .replaceWith( () ->
                     return $("
", attrs).append($(@).contents()) )
@@ -252,6 +245,15 @@
                 $('.active input').focus()
                 question++
                 question_info = @input_fields[question]
+
+            # Grid-List controls (already processed to a grid-list)
+            else if $('#' + @input_fields[question].name + '_field').hasClass('grid-list')
+              question_change = '#' + @input_fields[question].name + "_field"
+              $(question_change).fadeIn(1).addClass('active')
+              $(question_change + ' tr').each( () ->
+                $(@).fadeIn(1).addClass('active')
+              )
+              #question_change.children().fadeIn(1).addClass('active')

           # Assumption of a group without controls
           else
@@ -262,10 +264,9 @@
               else
                 if question > 0
                   question++
-            switch_question = $('#' + $($('.control-group').eq(question)[0]).data('key') + "_field")
-            switch_question.fadeIn(1).addClass('active')
-
-          console.log(@)
+            question_change = $('#' + $($('.control-group').eq(question)[0]).data('key') + "_field")
+            question_change.fadeIn(1).addClass('active')
+
           @

@@ -387,8 +388,6 @@

             @_display_form_buttons( question_index )

-            console.log(@)
-
             @

         next_question: () ->
@@ -406,7 +405,7 @@
             else if question_index < @input_fields.length
                 question_index += 1

-            @switch_question( $( '.control-group' ).eq( question_index )[0], true )
+            @switch_question( $( '#' + @input_fields[question_index].name + '_field' ), true )

             @

@@ -432,7 +431,7 @@
             else
               question_index -= 1

-            @switch_question( $( '.control-group' ).eq( question_index )[0], false )
+            @switch_question( $( '#' + @input_fields[question_index].name + '_field' ), false )

             @


Remove a debugging line.

Remove a debugging line.

--- CoffeeScripts/imonkey.coffee
+++ CoffeeScripts/imonkey.coffee
@@ -16,7 +16,6 @@
     @listeners = {}

   addEventListener: (type, listener) ->
-    awefawfewf.awefwafeewf() unless listener instanceof Function
     @listeners[type] ||= []
     @listeners[type].push(listener)


Updating postgres_session resource snippet

--- snippets/postgres_session.cson
+++ snippets/postgres_session.cson
@@ -1,4 +1,4 @@
 '.source.inspec':
   'Describe postgres_session':
     'prefix': 'postgres_session'
-    'body': "# http://inspec.io/docs/reference/resources/postgres_session/\ndescribe postgres_session('${1:name}') do\n  it { should${2:_not} ${3:exist} }${4}\nend"
+    'body': "# http://inspec.io/docs/reference/resources/postgres_session/\nsql = postgres_session('${1:username}', '${2:password}')\n\ndescribe sql.query('${3:QUERY}') do\n  its('stdout') { should${4:_not} match(/${5:text}/) }${6}\nend"


Adds unobtrusiveness helpers

Adds unobtrusiveness helpers

--- app/assets/javascripts/bootstrap_patches.js.coffee
+++ app/assets/javascripts/bootstrap_patches.js.coffee
@@ -1,3 +1,19 @@
+#  Custom unobtrusiveness helpers
+
+$.unobtrusive = (callback) ->
+  $(document).bind('activate-unobtrusive-javascript', (event, root) ->
+    $(root).each(callback)
+  )
+
+$.fn.activateUnobtrusiveJavascript = () ->
+  return this.each(() ->
+    $(document).trigger('activate-unobtrusive-javascript', this)
+  )
+
+$(document).ready () ->
+  $(document).activateUnobtrusiveJavascript()
+
+
 # Fixes Bootstrap dropdownmenus on iOS

 $(document).ready () ->
@@ -12,7 +28,7 @@

 # Fixes data-dismiss

-$(document).ready () ->
+$.unobtrusive () ->
   $('[data-dismiss]').on('click', () ->
     selector = '.' + $(this).data('dismiss')
     $(this).closest(selector).fadeOut('fast')


trying to make a content-wrappable tag

trying to make a content-wrappable tag

--- app/assets/javascripts/mercury/regions/full.coffee
+++ app/assets/javascripts/mercury/regions/full.coffee
@@ -246,11 +246,12 @@
       # replace snippet contents to be an identifier
       if filterSnippets then for element, index in container.find('[data-snippet]')
         element = jQuery(element)
-        if snippet = Mercury.Snippet.find(element.data("snippet"))
-          Mercury.log("adding data to snippet #{snippet.identity}: #{element.html()}")
-          snippet.data = element.html()
-        element.html("[#{element.data("snippet")}/#{element.data("version")}]")
-        element.attr({contenteditable: null, 'data-version': null})
+        if element.data('wrap-content') != 'true'
+          if snippet = Mercury.Snippet.find(element.data("snippet"))
+            Mercury.log("adding data to snippet #{snippet.identity}: #{element.html()}")
+            snippet.data = element.html()
+          element.html("[#{element.data("snippet")}/#{element.data("version")}]")
+          element.attr({contenteditable: null, 'data-version': null})
         if saving && Mercury.config.serverParser.radius
           element.attr({'class': null})
           remove_attrs = []


increase late time padding on quizzes in gradebook2

adjusted up to a minute for slow server responses
when a quiz is submitted

fixes #7882

Change-Id: Id607fd76ce1e490d66799c54d61419696ed31218
Reviewed-on: https://gerrit.instructure.com/12818
Reviewed-by: Jon Jensen <44f878afe53efc66b76772bd845eb65944ed8232@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>

--- app/coffeescripts/gradebook2/SubmissionCell.coffee
+++ app/coffeescripts/gradebook2/SubmissionCell.coffee
@@ -84,7 +84,7 @@
       classes.push('resubmitted') if submission.grade_matches_current_submission == false
       if assignment.due_at && submission.submitted_at
         classes.push('late') if submission.submission_type isnt 'online_quiz' && (submission.submitted_at.timestamp > assignment.due_at.timestamp)
-        classes.push('late') if submission.submission_type is 'online_quiz' && ((submission.submitted_at.timestamp - assignment.due_at.timestamp) > 30)
+        classes.push('late') if submission.submission_type is 'online_quiz' && ((submission.submitted_at.timestamp - assignment.due_at.timestamp) > 60)
       classes.push('dropped') if submission.drop
       classes.push('ungraded') if ''+assignment.submission_types is "not_graded"
       classes.push('muted') if assignment.muted


ListViewController: ListViewController::instantiateListItems to use ListViewController::addItem instead of ListViewController::listView

--- src/components/list/listviewcontroller.coffee
+++ src/components/list/listviewcontroller.coffee
@@ -102,9 +102,9 @@

   instantiateListItems: (items) ->

-    newItems = (@getListView().addItem itemData for itemData in items)
-
-    @emit "AllItemsAddedToList"
+    newItems = (@addItem itemData for itemData in items)
+
+    @emit 'AllItemsAddedToList'

     return newItems


use v_select where possible

use v_select where possible

--- subtree/bokehjs/static/coffee/renderers/glyph/glyph.coffee
+++ subtree/bokehjs/static/coffee/renderers/glyph/glyph.coffee
@@ -53,7 +53,7 @@
     if      pt == 'x' then mapper = @plot_view.xmapper
     else if pt == 'y' then mapper = @plot_view.ymapper

-    span = (@glyph_props.select(span, x) for x in data)
+    span = @glyph_props.v_select(span, data)
     if span_units == 'screen'
       return span


Add a test to check that users are summarised properly even when a null user occurs earlier in the update list.

--- test/unit/coffee/UpdatesManager/UpdatesManagerTests.coffee
+++ test/unit/coffee/UpdatesManager/UpdatesManagerTests.coffee
@@ -655,15 +655,8 @@
 					end_ts:   @now + 30
 			}]

-		it "should roll several null user values into one", ->
+		it "should include null user values, when the null is earlier in the updates list", ->
 			result = @UpdatesManager._summarizeUpdates [{
-				doc_id: "doc-id-1"
-				meta:
-					user: @user_1
-					start_ts: @now + 20
-					end_ts:   @now + 30
-				v: 5
-			}, {
 				doc_id: "doc-id-1"
 				meta:
 					user: null
@@ -673,10 +666,10 @@
 			}, {
 				doc_id: "doc-id-1"
 				meta:
-					user: null
-					start_ts: @now + 2
-					end_ts:   @now + 4
-				v: 4
+					user: @user_1
+					start_ts: @now + 20
+					end_ts:   @now + 30
+				v: 5
 			}]
 			expect(result).to.deep.equal [{
 				docs:
@@ -684,6 +677,40 @@
 						fromV: 4
 						toV: 5
 				meta:
+					users: [null, @user_1]
+					start_ts: @now
+					end_ts:   @now + 30
+			}]
+
+		it "should roll several null user values into one", ->
+			result = @UpdatesManager._summarizeUpdates [{
+				doc_id: "doc-id-1"
+				meta:
+					user: @user_1
+					start_ts: @now + 20
+					end_ts:   @now + 30
+				v: 5
+			}, {
+				doc_id: "doc-id-1"
+				meta:
+					user: null
+					start_ts: @now
+					end_ts:   @now + 10
+				v: 4
+			}, {
+				doc_id: "doc-id-1"
+				meta:
+					user: null
+					start_ts: @now + 2
+					end_ts:   @now + 4
+				v: 4
+			}]
+			expect(result).to.deep.equal [{
+				docs:
+					"doc-id-1":
+						fromV: 4
+						toV: 5
+				meta:
 					users: [@user_1, null]
 					start_ts: @now
 					end_ts:   @now + 30


Campaign: removed log

Campaign: removed log

--- workers/social/lib/social/models/campaigns/campaign.coffee
+++ workers/social/lib/social/models/campaigns/campaign.coffee
@@ -44,8 +44,6 @@

     JStorage.one { name }, (err, campaign) ->

-      console.log err, campaign
-
       return callback null, no  if err or not campaign or not campaign.content.active

       callback null, campaign


Set default value of todayBtn to false.

Set default value of todayBtn to false.

--- src/widgets/components/datetime-picker.coffee
+++ src/widgets/components/datetime-picker.coffee
@@ -3,7 +3,7 @@

   format: 'yyyy-mm-dd hh:ii'
   autoclose: true
-  todayBtn: true
+  todayBtn: false
   startDate: '1949-10-01'
   minuteStep: 10
   minView: 0